[*] 服务端录制
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
package com.acgist.taoyao.signal.config.camera;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* AI识别配置
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(title = "AI识别配置", description = "AI识别配置")
|
||||
public class AiProperties {
|
||||
|
||||
/**
|
||||
* 识别类型
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
// 人
|
||||
PERSON;
|
||||
|
||||
}
|
||||
|
||||
@Schema(title = "是否开启", description = "是否开启")
|
||||
@NotNull(message = "没有指定操作状态")
|
||||
private Boolean enabled;
|
||||
@Schema(title = "识别类型", description = "识别类型")
|
||||
private Type type;
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.acgist.taoyao.signal.config.camera;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 美颜配置
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(title = "美颜配置", description = "美颜配置")
|
||||
public class BeautyProperties {
|
||||
|
||||
@Schema(title = "是否开启", description = "是否开启")
|
||||
@NotNull(message = "没有指定操作状态")
|
||||
private Boolean enabled;
|
||||
@Schema(title = "美颜级别", description = "美颜级别")
|
||||
private Integer level;
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.acgist.taoyao.signal.config.camera;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 摄像头配置
|
||||
*
|
||||
* 音频:
|
||||
* 混音:不用混音
|
||||
* 变声:
|
||||
* 降噪:
|
||||
*
|
||||
* 视频:
|
||||
* 录制:
|
||||
* 水印:
|
||||
* 美颜:
|
||||
* AI识别:
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(title = "摄像头配置", description = "摄像头配置")
|
||||
@ConfigurationProperties(prefix = "taoyao.camera")
|
||||
public class CameraProperties {
|
||||
|
||||
@Schema(title = "混音", description = "混音")
|
||||
private Boolean audioMixer = Boolean.FALSE;
|
||||
@Schema(title = "变声", description = "变声")
|
||||
private Boolean audioChanger;
|
||||
@Schema(title = "降噪", description = "降噪")
|
||||
private Boolean audioDenoise;
|
||||
@Schema(title = "存储目录", description = "存储目录")
|
||||
private String storagePath;
|
||||
@Schema(title = "图片存储目录", description = "图片存储目录")
|
||||
private String storageImagePath;
|
||||
@Schema(title = "视频存储目录", description = "视频存储目录")
|
||||
private String storageVideoPath;
|
||||
@Schema(title = "AI识别配置", description = "AI识别配置")
|
||||
private AiProperties ai;
|
||||
@Schema(title = "美颜配置", description = "美颜配置")
|
||||
private BeautyProperties beauty;
|
||||
@Schema(title = "水印配置", description = "水印配置")
|
||||
private WatermarkProperties watermark;
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.acgist.taoyao.signal.config.camera;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 水印配置
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Schema(title = "水印配置", description = "水印配置")
|
||||
public class WatermarkProperties {
|
||||
|
||||
@Schema(title = "是否开启", description = "是否开启")
|
||||
@NotNull(message = "没有指定操作状态")
|
||||
private Boolean enabled;
|
||||
@Schema(title = "水印内容", description = "水印内容")
|
||||
private String text;
|
||||
@Schema(title = "X坐标", description = "X坐标")
|
||||
private Integer posx;
|
||||
@Schema(title = "Y坐标", description = "Y坐标")
|
||||
private Integer posy;
|
||||
@Schema(title = "透明度", description = "透明度")
|
||||
private Double opacity;
|
||||
|
||||
}
|
||||
@@ -2,12 +2,10 @@ package com.acgist.taoyao.signal.configuration;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import com.acgist.taoyao.boot.runner.OrderedCommandLineRunner;
|
||||
import com.acgist.taoyao.signal.config.camera.CameraProperties;
|
||||
import com.acgist.taoyao.signal.event.EventPublisher;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolManager;
|
||||
|
||||
@@ -21,9 +19,6 @@ import lombok.RequiredArgsConstructor;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@RequiredArgsConstructor
|
||||
@EnableConfigurationProperties({
|
||||
CameraProperties.class
|
||||
})
|
||||
public class SignalAutoConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.acgist.taoyao.signal.model.control;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* PTZ控制参数
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Schema(title = "PTZ控制参数", description = "PTZ控制参数")
|
||||
public class PtzModel {
|
||||
|
||||
/**
|
||||
* PTZ类型
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public enum Type {
|
||||
|
||||
// 水平
|
||||
PAN,
|
||||
// 垂直
|
||||
TILT,
|
||||
// 变焦
|
||||
ZOOM;
|
||||
|
||||
}
|
||||
|
||||
@Schema(title = "PTZ类型", description = "PTZ类型")
|
||||
@NotNull(message = "PTZ类型不能为空")
|
||||
private Type type;
|
||||
@Schema(title = "PTZ参数", description = "PTZ参数")
|
||||
@NotNull(message = "PTZ参数不能为空")
|
||||
private Double value;
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@ public class ClientWrapper implements AutoCloseable {
|
||||
* SCTP协商
|
||||
*/
|
||||
private Object sctpCapabilities;
|
||||
/**
|
||||
* 媒体录像
|
||||
*/
|
||||
private Recorder recorder;
|
||||
/**
|
||||
* 发送通道
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.acgist.taoyao.signal.party.media;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.acgist.taoyao.boot.config.FfmpegProperties;
|
||||
import com.acgist.taoyao.boot.utils.FileUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 媒体录像
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
public class Recorder {
|
||||
|
||||
/**
|
||||
* 是否关闭
|
||||
*/
|
||||
private boolean close;
|
||||
/**
|
||||
* 是否正在运行
|
||||
*/
|
||||
private boolean running;
|
||||
/**
|
||||
* 音频端口
|
||||
*/
|
||||
private Integer audioPort;
|
||||
/**
|
||||
* 视频端口
|
||||
*/
|
||||
private Integer videoPort;
|
||||
/**
|
||||
* 传输通道
|
||||
*/
|
||||
private Transport transport;
|
||||
/**
|
||||
* 音频消费者
|
||||
*/
|
||||
private Consumer audioConsumer;
|
||||
/**
|
||||
* 视频消费者
|
||||
*/
|
||||
private Consumer videoConsumer;
|
||||
/**
|
||||
* 录像进程
|
||||
*/
|
||||
private Process process;
|
||||
/**
|
||||
* 进程Builder
|
||||
*/
|
||||
private ProcessBuilder processBuilder;
|
||||
/**
|
||||
* 录制线程
|
||||
*/
|
||||
private Thread thread;
|
||||
/**
|
||||
* 日志线程
|
||||
*/
|
||||
private Thread inputThread;
|
||||
/**
|
||||
* 异常线程
|
||||
*/
|
||||
private Thread errorThread;
|
||||
/**
|
||||
* 命令
|
||||
*/
|
||||
private String command;
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
private final String folder;
|
||||
/**
|
||||
* SDP路径
|
||||
*/
|
||||
private final String sdpfile;
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
private final String filepath;
|
||||
/**
|
||||
* FFmpeg配置
|
||||
*/
|
||||
private final FfmpegProperties ffmpegProperties;
|
||||
|
||||
public Recorder(FfmpegProperties ffmpegProperties) {
|
||||
this.close = false;
|
||||
this.running = false;
|
||||
this.ffmpegProperties = ffmpegProperties;
|
||||
final String id = UUID.randomUUID().toString();
|
||||
this.folder = Paths.get(ffmpegProperties.getStorageVideoPath(), id).toAbsolutePath().toString();
|
||||
this.sdpfile = Paths.get(this.folder, "taoyao.sdp").toAbsolutePath().toString();
|
||||
this.filepath = Paths.get(this.folder, "taoyao.mp4").toAbsolutePath().toString();
|
||||
this.command = String.format(this.ffmpegProperties.getRecord(), this.sdpfile, this.filepath);
|
||||
FileUtils.mkdirs(this.folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始录像
|
||||
*/
|
||||
public void start() {
|
||||
synchronized (this) {
|
||||
if(this.running) {
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
this.thread = new Thread(this::record);
|
||||
this.thread.setDaemon(true);
|
||||
this.thread.setName("TaoyaoRecord");
|
||||
this.thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 录制视频
|
||||
*/
|
||||
private void record() {
|
||||
this.buildSdpfile();
|
||||
int status = 0;
|
||||
final StringBuilder input = new StringBuilder();
|
||||
final StringBuilder error = new StringBuilder();
|
||||
try {
|
||||
final boolean linux = FileUtils.linux();
|
||||
if(linux) {
|
||||
this.processBuilder = new ProcessBuilder("/bin/bash", "-c", this.command);
|
||||
this.process = processBuilder.start();
|
||||
} else {
|
||||
this.processBuilder = new ProcessBuilder("cmd", "/c", this.command);
|
||||
this.process = processBuilder.start();
|
||||
}
|
||||
log.debug("""
|
||||
开始录像:{}
|
||||
录像命令:{}
|
||||
""", this.filepath, this.command);
|
||||
this.inputThread = new Thread(() -> {
|
||||
try (final InputStream inputStream = this.process.getInputStream()) {
|
||||
int length;
|
||||
final byte[] bytes = new byte[1024];
|
||||
while(this.running && !this.close && (length = inputStream.read(bytes)) >= 0) {
|
||||
input.append(linux ? new String(bytes, 0, length) : new String(bytes, 0, length, "GBK"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("读取录像日志异常", e);
|
||||
}
|
||||
});
|
||||
this.inputThread.setDaemon(true);
|
||||
this.inputThread.setName("TaoyaoRecordInput");
|
||||
this.inputThread.start();
|
||||
this.errorThread = new Thread(() -> {
|
||||
try (final InputStream inputStream = this.process.getErrorStream();) {
|
||||
int length;
|
||||
final byte[] bytes = new byte[1024];
|
||||
while(this.running && !this.close && (length = inputStream.read(bytes)) >= 0) {
|
||||
error.append(linux ? new String(bytes, 0, length) : new String(bytes, 0, length, "GBK"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("读取录像错误异常", e);
|
||||
}
|
||||
});
|
||||
this.errorThread.setDaemon(true);
|
||||
this.errorThread.setName("TaoyaoRecordError");
|
||||
this.errorThread.start();
|
||||
status = this.process.waitFor();
|
||||
} catch (Exception e) {
|
||||
log.error("录像异常:{}", this.command, e);
|
||||
} finally {
|
||||
this.stop();
|
||||
}
|
||||
log.debug("""
|
||||
结束录像:{}
|
||||
结束状态:{}
|
||||
录像日志:{}
|
||||
异常日志:{}
|
||||
""", this.filepath, status, input, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SDP文件
|
||||
*/
|
||||
private void buildSdpfile() {
|
||||
try {
|
||||
Files.write(
|
||||
Paths.get(this.sdpfile),
|
||||
String.format(this.ffmpegProperties.getSdp(), 8888, 9999).getBytes(),
|
||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE
|
||||
);
|
||||
} catch (IOException e) {
|
||||
log.error("创建SDP文件异常:{}", this.sdpfile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束录像
|
||||
*/
|
||||
public void stop() {
|
||||
synchronized (this) {
|
||||
if(this.close) {
|
||||
return;
|
||||
}
|
||||
this.close = true;
|
||||
}
|
||||
if(this.process == null) {
|
||||
return;
|
||||
}
|
||||
log.debug("结束媒体录像:{}", this.filepath);
|
||||
// 所有子进程
|
||||
this.process.children().forEach(process -> {
|
||||
process.destroy();
|
||||
});
|
||||
// 当前父进程
|
||||
this.process.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.acgist.taoyao.signal.protocol.control;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Description;
|
||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.client.Client;
|
||||
import com.acgist.taoyao.signal.client.ClientType;
|
||||
import com.acgist.taoyao.signal.config.camera.AiProperties;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter;
|
||||
|
||||
/**
|
||||
* 打开AI识别信令
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Protocol
|
||||
@Description(
|
||||
body = """
|
||||
{
|
||||
"to": "目标终端ID",
|
||||
...AiProperties
|
||||
}
|
||||
""",
|
||||
flow = {
|
||||
"信令服务->终端",
|
||||
"终端=>信令服务->终端"
|
||||
}
|
||||
)
|
||||
public class ControlAiProtocol extends ProtocolControlAdapter {
|
||||
|
||||
public static final String SIGNAL = "control::ai";
|
||||
|
||||
public ControlAiProtocol() {
|
||||
super("打开AI识别信令", SIGNAL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map<String, Object> body) {
|
||||
client.push(targetClient.request(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId 终端标识
|
||||
* @param aiProperties AI识别配置
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
public Message execute(String clientId, AiProperties aiProperties) {
|
||||
return this.request(clientId, this.build(aiProperties));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.acgist.taoyao.signal.protocol.control;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Description;
|
||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.client.Client;
|
||||
import com.acgist.taoyao.signal.client.ClientType;
|
||||
import com.acgist.taoyao.signal.config.camera.BeautyProperties;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter;
|
||||
|
||||
/**
|
||||
* 打开美颜信令
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Protocol
|
||||
@Description(
|
||||
body = """
|
||||
{
|
||||
"to": "目标终端ID",
|
||||
...BeautyProperties
|
||||
}
|
||||
""",
|
||||
flow = {
|
||||
"信令服务->终端",
|
||||
"终端=>信令服务->终端"
|
||||
}
|
||||
)
|
||||
public class ControlBeautyProtocol extends ProtocolControlAdapter {
|
||||
|
||||
public static final String SIGNAL = "control::beauty";
|
||||
|
||||
public ControlBeautyProtocol() {
|
||||
super("打开美颜信令", SIGNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map<String, Object> body) {
|
||||
client.push(targetClient.request(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId 终端ID
|
||||
* @param beautyProperties 美颜配置
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
public Message execute(String clientId, BeautyProperties beautyProperties) {
|
||||
return this.request(clientId, this.build(beautyProperties));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.acgist.taoyao.signal.protocol.control;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Description;
|
||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.client.Client;
|
||||
import com.acgist.taoyao.signal.client.ClientType;
|
||||
import com.acgist.taoyao.signal.model.control.PtzModel;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter;
|
||||
|
||||
/**
|
||||
* PTZ信令
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Protocol
|
||||
@Description(
|
||||
body = """
|
||||
{
|
||||
"to": "目标终端ID",
|
||||
...PtzControl
|
||||
}
|
||||
""",
|
||||
flow = {
|
||||
"信令服务->终端",
|
||||
"终端->信令服务->终端"
|
||||
}
|
||||
)
|
||||
public class ControlPtzProtocol extends ProtocolControlAdapter {
|
||||
|
||||
public static final String SIGNAL = "control::ptz";
|
||||
|
||||
public ControlPtzProtocol() {
|
||||
super("PTZ信令", SIGNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map<String, Object> body) {
|
||||
client.push(targetClient.request(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId 终端标识
|
||||
* @param ptzModel PTZ控制参数
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
public Message execute(String clientId, PtzModel ptzModel) {
|
||||
return this.request(clientId, this.build(ptzModel));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.acgist.taoyao.signal.protocol.control;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Description;
|
||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.client.Client;
|
||||
import com.acgist.taoyao.signal.client.ClientType;
|
||||
import com.acgist.taoyao.signal.config.camera.WatermarkProperties;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolControlAdapter;
|
||||
|
||||
/**
|
||||
* 配置水印信令
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
@Protocol
|
||||
@Description(
|
||||
memo = "如果没有指定参数使用默认参数配置",
|
||||
body = """
|
||||
{
|
||||
"to": "目标终端ID",
|
||||
...WatermarkProperties
|
||||
}
|
||||
""",
|
||||
flow = {
|
||||
"信令服务->终端",
|
||||
"终端=>信令服务->终端"
|
||||
}
|
||||
)
|
||||
public class ControlWatermarkProtocol extends ProtocolControlAdapter {
|
||||
|
||||
public static final String SIGNAL = "control::watermark";
|
||||
|
||||
public ControlWatermarkProtocol() {
|
||||
super("配置水印信令", SIGNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String clientId, ClientType clientType, Client client, Client targetClient, Message message, Map<String, Object> body) {
|
||||
client.push(targetClient.request(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId 终端ID
|
||||
* @param watermarkProperties 水印配置
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
public Message execute(String clientId, WatermarkProperties watermarkProperties) {
|
||||
return this.request(clientId, this.build(watermarkProperties));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,84 @@
|
||||
package com.acgist.taoyao.signal.protocol.media;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.acgist.taoyao.boot.annotation.Description;
|
||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||
import com.acgist.taoyao.boot.config.FfmpegProperties;
|
||||
import com.acgist.taoyao.boot.model.Message;
|
||||
import com.acgist.taoyao.signal.client.Client;
|
||||
import com.acgist.taoyao.signal.client.ClientType;
|
||||
import com.acgist.taoyao.signal.party.media.ClientWrapper;
|
||||
import com.acgist.taoyao.signal.party.media.Recorder;
|
||||
import com.acgist.taoyao.signal.party.media.Room;
|
||||
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
||||
|
||||
/**
|
||||
* 媒体录制
|
||||
* 使用ffmpeg进行录制代码侵入较低
|
||||
* 媒体录像
|
||||
*
|
||||
* @author acgist
|
||||
*/
|
||||
public class MediaRecordProtocol {
|
||||
@Protocol
|
||||
@Description(
|
||||
body = {
|
||||
"""
|
||||
{
|
||||
"clientId": "目标终端ID",
|
||||
"enabled": 是否录像(true|false)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
{
|
||||
"enabled": 是否录像(true|false),
|
||||
"filepath": "视频文件路径"
|
||||
}
|
||||
"""
|
||||
},
|
||||
flow = "终端=>信令服务->终端"
|
||||
)
|
||||
public class MediaRecordProtocol extends ProtocolRoomAdapter {
|
||||
|
||||
private final FfmpegProperties ffmpegProperties;
|
||||
|
||||
public MediaRecordProtocol(FfmpegProperties ffmpegProperties) {
|
||||
super("媒体录像", "media::record");
|
||||
this.ffmpegProperties = ffmpegProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param roomId 房间ID
|
||||
* @param clientId 终端ID
|
||||
* @param enabled 状态
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
public Message execute(String roomId, String clientId, Boolean enabled) {
|
||||
final Room room = this.roomManager.room(roomId);
|
||||
final Client client = this.clientManager.clients(clientId);
|
||||
if(enabled) {
|
||||
this.record(room, client);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始录制
|
||||
*/
|
||||
private void record(Room room, Client client) {
|
||||
final ClientWrapper clientWrapper = room.clientWrapper(client);
|
||||
synchronized (clientWrapper) {
|
||||
if(clientWrapper.getRecorder() != null) {
|
||||
return;
|
||||
}
|
||||
final Recorder recorder = new Recorder(this.ffmpegProperties);
|
||||
recorder.start();
|
||||
clientWrapper.setRecorder(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user