[*] rtp
This commit is contained in:
@@ -26,6 +26,10 @@
|
|||||||
|taoyao-client-harmony|鸿蒙终端|鸿蒙智能终端接入|
|
|taoyao-client-harmony|鸿蒙终端|鸿蒙智能终端接入|
|
||||||
|taoyao-signal-server|信令服务|终端信令控制|
|
|taoyao-signal-server|信令服务|终端信令控制|
|
||||||
|
|
||||||
|
## 证书
|
||||||
|
|
||||||
|
本地开发测试安装`docs/certs`中的`ca.crt`证书
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
|
|
||||||
[部署文档](./docs/Deploy.md)
|
[部署文档](./docs/Deploy.md)
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# 测试
|
|
||||||
|
|
||||||
## 推流测试
|
|
||||||
|
|
||||||
```
|
|
||||||
ffmpeg
|
|
||||||
```
|
|
||||||
@@ -19,7 +19,7 @@ http {
|
|||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_types text/xml text/css text/plain text/javascript image/gif image/png image/jpg image/webp image/jpeg image/x-icon image/svg+xml application/json application/javascript font/woff application/octet-stream application/vnd.ms-fontobject;
|
gzip_types font/woff text/xml text/css text/plain text/javascript image/gif image/png image/jpg image/webp image/jpeg image/x-icon image/svg+xml application/json application/javascript application/octet-stream application/vnd.ms-fontobject;
|
||||||
gzip_min_length 1k;
|
gzip_min_length 1k;
|
||||||
|
|
||||||
sendfile on;
|
sendfile on;
|
||||||
|
|||||||
@@ -119,12 +119,12 @@ module.exports = {
|
|||||||
announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || defaultTaoyaoHost || "127.0.0.1",
|
announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || defaultTaoyaoHost || "127.0.0.1",
|
||||||
},
|
},
|
||||||
// TCP
|
// TCP
|
||||||
// {
|
{
|
||||||
// protocol: "tcp",
|
protocol: "tcp",
|
||||||
// ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0",
|
ip: process.env.MEDIASOUP_LISTEN_IP || "0.0.0.0",
|
||||||
// port: 44444,
|
port: 44444,
|
||||||
// announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || defaultTaoyaoHost || "127.0.0.1",
|
announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || defaultTaoyaoHost || "127.0.0.1",
|
||||||
// },
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// WebRtcTransport:https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
|
// WebRtcTransport:https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
|
||||||
|
|||||||
@@ -452,8 +452,8 @@ class Taoyao {
|
|||||||
case "media::transport::close":
|
case "media::transport::close":
|
||||||
this.mediaTransportClose(message, body);
|
this.mediaTransportClose(message, body);
|
||||||
break;
|
break;
|
||||||
case "media::transport::plain::in":
|
case "media::transport::plain":
|
||||||
me.mediaTransportPlainIn(message, body);
|
me.mediaTransportPlain(message, body);
|
||||||
break;
|
break;
|
||||||
case "media::transport::webrtc::connect":
|
case "media::transport::webrtc::connect":
|
||||||
me.mediaTransportWebrtcConnect(message, body);
|
me.mediaTransportWebrtcConnect(message, body);
|
||||||
@@ -629,8 +629,8 @@ class Taoyao {
|
|||||||
producer.on("videoorientationchange", (videoOrientation) => {
|
producer.on("videoorientationchange", (videoOrientation) => {
|
||||||
console.info("producer videoorientationchange:", producer.id, videoOrientation);
|
console.info("producer videoorientationchange:", producer.id, videoOrientation);
|
||||||
self.push(protocol.buildMessage("media::video::orientation::change", {
|
self.push(protocol.buildMessage("media::video::orientation::change", {
|
||||||
|
...videoOrientation,
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
...videoOrientation
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
// await producer.enableTraceEvent([ 'rtp', 'keyframe', 'nack', 'pli', 'fir' ]);
|
// await producer.enableTraceEvent([ 'rtp', 'keyframe', 'nack', 'pli', 'fir' ]);
|
||||||
@@ -821,6 +821,7 @@ class Taoyao {
|
|||||||
});
|
});
|
||||||
consumer.on("producerpause", () => {
|
consumer.on("producerpause", () => {
|
||||||
console.info("consumer producerpause:", consumer.id);
|
console.info("consumer producerpause:", consumer.id);
|
||||||
|
// consumer.pause();
|
||||||
this.push(
|
this.push(
|
||||||
protocol.buildMessage("media::consumer::pause", {
|
protocol.buildMessage("media::consumer::pause", {
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
@@ -830,6 +831,7 @@ class Taoyao {
|
|||||||
});
|
});
|
||||||
consumer.on("producerresume", () => {
|
consumer.on("producerresume", () => {
|
||||||
console.info("consumer producerresume:", consumer.id);
|
console.info("consumer producerresume:", consumer.id);
|
||||||
|
// consumer.resume();
|
||||||
this.push(
|
this.push(
|
||||||
protocol.buildMessage("media::consumer::resume", {
|
protocol.buildMessage("media::consumer::resume", {
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
@@ -1261,16 +1263,21 @@ class Taoyao {
|
|||||||
* @param {*} message 消息
|
* @param {*} message 消息
|
||||||
* @param {*} body 消息主体
|
* @param {*} body 消息主体
|
||||||
*/
|
*/
|
||||||
async mediaTransportPlainIn(message, body) {
|
async mediaTransportPlain(message, body) {
|
||||||
const me = this;
|
const me = this;
|
||||||
const { roomId, rtcpMux, comedia, clientId } = body;
|
const { roomId, rtcpMux, comedia, clientId, enableSctp, numSctpStreams, enableSrtp, srtpCryptoSuite } = body;
|
||||||
const plainTransportOptions = {
|
const plainTransportOptions = {
|
||||||
|
...config.mediasoup.plainTransportOptions,
|
||||||
rtcpMux: rtcpMux,
|
rtcpMux: rtcpMux,
|
||||||
comedia: comedia,
|
comedia: comedia,
|
||||||
...config.mediasoup.plainTransportOptions,
|
enableSctp: enableSctp || Boolean(numSctpStreams),
|
||||||
|
numSctpStreams: numSctpStreams || 0,
|
||||||
|
enableSrtp: enableSrtp,
|
||||||
|
srtpCryptoSuite: srtpCryptoSuite,
|
||||||
};
|
};
|
||||||
const room = this.rooms.get(roomId);
|
const room = this.rooms.get(roomId);
|
||||||
const transport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions);
|
const transport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions);
|
||||||
|
me.transportEvent("plain", roomId, transport);
|
||||||
transport.clientId = clientId;
|
transport.clientId = clientId;
|
||||||
room.transports.set(transport.id, transport);
|
room.transports.set(transport.id, transport);
|
||||||
console.info(transport.tuple)
|
console.info(transport.tuple)
|
||||||
@@ -1336,8 +1343,35 @@ class Taoyao {
|
|||||||
...webRtcTransportOptions,
|
...webRtcTransportOptions,
|
||||||
webRtcServer: room.webRtcServer,
|
webRtcServer: room.webRtcServer,
|
||||||
});
|
});
|
||||||
|
me.transportEvent("webrtc", roomId, transport);
|
||||||
transport.clientId = clientId;
|
transport.clientId = clientId;
|
||||||
// 通用事件
|
room.transports.set(transport.id, transport);
|
||||||
|
message.body = {
|
||||||
|
roomId: roomId,
|
||||||
|
transportId: transport.id,
|
||||||
|
iceCandidates: transport.iceCandidates,
|
||||||
|
iceParameters: transport.iceParameters,
|
||||||
|
dtlsParameters: transport.dtlsParameters,
|
||||||
|
sctpParameters: transport.sctpParameters,
|
||||||
|
};
|
||||||
|
self.push(message);
|
||||||
|
const { maxIncomingBitrate } = config.mediasoup.webRtcTransportOptions;
|
||||||
|
// If set, apply max incoming bitrate limit.
|
||||||
|
if (maxIncomingBitrate) {
|
||||||
|
try {
|
||||||
|
await transport.setMaxIncomingBitrate(maxIncomingBitrate);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 通道事件
|
||||||
|
*
|
||||||
|
* @param {*} type 类型:webrtc|plain|pipe|direct
|
||||||
|
* @param {*} roomId 房间ID
|
||||||
|
* @param {*} transport 通道
|
||||||
|
*/
|
||||||
|
transportEvent(type, roomId, transport) {
|
||||||
|
/********************* 通用通道事件 *********************/
|
||||||
transport.on("routerclose", () => {
|
transport.on("routerclose", () => {
|
||||||
console.info("transport routerclose:", transport.id);
|
console.info("transport routerclose:", transport.id);
|
||||||
transport.close();
|
transport.close();
|
||||||
@@ -1377,6 +1411,7 @@ class Taoyao {
|
|||||||
// });
|
// });
|
||||||
// transport.observer.on("trace", fn(trace));
|
// transport.observer.on("trace", fn(trace));
|
||||||
/********************* webRtcTransport通道事件 *********************/
|
/********************* webRtcTransport通道事件 *********************/
|
||||||
|
if("webrtc" === type) {
|
||||||
// transport.on("icestatechange", (iceState) => {
|
// transport.on("icestatechange", (iceState) => {
|
||||||
// console.info("transport icestatechange:", transport.id, iceState);
|
// console.info("transport icestatechange:", transport.id, iceState);
|
||||||
// });
|
// });
|
||||||
@@ -1393,34 +1428,24 @@ class Taoyao {
|
|||||||
// transport.observer.on("iceselectedtuplechange", fn(iceSelectedTuple));
|
// transport.observer.on("iceselectedtuplechange", fn(iceSelectedTuple));
|
||||||
// transport.observer.on("dtlsstatechange", fn(dtlsState));
|
// transport.observer.on("dtlsstatechange", fn(dtlsState));
|
||||||
// transport.observer.on("sctpstatechange", fn(sctpState));
|
// transport.observer.on("sctpstatechange", fn(sctpState));
|
||||||
|
}
|
||||||
/********************* plainTransport通道事件 *********************/
|
/********************* plainTransport通道事件 *********************/
|
||||||
|
if("plain" === type) {
|
||||||
// transport.on("tuple", fn(tuple));
|
// transport.on("tuple", fn(tuple));
|
||||||
// transport.on("rtcptuple", fn(rtcpTuple));
|
// transport.on("rtcptuple", fn(rtcpTuple));
|
||||||
// transport.on("sctpstatechange", fn(sctpState));
|
// transport.on("sctpstatechange", fn(sctpState));
|
||||||
// transport.observer.on("tuple", fn(tuple));
|
// transport.observer.on("tuple", fn(tuple));
|
||||||
// transport.observer.on("rtcptuple", fn(rtcpTuple));
|
// transport.observer.on("rtcptuple", fn(rtcpTuple));
|
||||||
// transport.observer.on("sctpstatechange", fn(sctpState));
|
// transport.observer.on("sctpstatechange", fn(sctpState));
|
||||||
|
}
|
||||||
/********************* pipeTransport通道事件 *********************/
|
/********************* pipeTransport通道事件 *********************/
|
||||||
|
if("pipe" === type) {
|
||||||
// transport.on("sctpstatechange", fn(sctpState));
|
// transport.on("sctpstatechange", fn(sctpState));
|
||||||
// transport.observer.on("sctpstatechange", fn(sctpState));
|
// transport.observer.on("sctpstatechange", fn(sctpState));
|
||||||
|
}
|
||||||
/********************* directTransport通道事件 *********************/
|
/********************* directTransport通道事件 *********************/
|
||||||
|
if("rtcp" === type) {
|
||||||
// transport.on("rtcp", fn(rtcpPacket));
|
// transport.on("rtcp", fn(rtcpPacket));
|
||||||
room.transports.set(transport.id, transport);
|
|
||||||
message.body = {
|
|
||||||
roomId: roomId,
|
|
||||||
transportId: transport.id,
|
|
||||||
iceCandidates: transport.iceCandidates,
|
|
||||||
iceParameters: transport.iceParameters,
|
|
||||||
dtlsParameters: transport.dtlsParameters,
|
|
||||||
sctpParameters: transport.sctpParameters,
|
|
||||||
};
|
|
||||||
self.push(message);
|
|
||||||
const { maxIncomingBitrate } = config.mediasoup.webRtcTransportOptions;
|
|
||||||
// If set, apply max incoming bitrate limit.
|
|
||||||
if (maxIncomingBitrate) {
|
|
||||||
try {
|
|
||||||
await transport.setMaxIncomingBitrate(maxIncomingBitrate);
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
# Web终端
|
# Web终端
|
||||||
|
|
||||||
注意部分`API`需要`localhost`或者`https`
|
|
||||||
|
|
||||||
## Web终端
|
## Web终端
|
||||||
|
|
||||||
* [mediasoup-client源码](https://github.com/versatica/mediasoup-client)
|
* [mediasoup-client源码](https://github.com/versatica/mediasoup-client)
|
||||||
* [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client)
|
* [mediasoup-client文档](https://mediasoup.org/documentation/v3/mediasoup-client)
|
||||||
* [mediasoup-client接口](https://mediasoup.org/documentation/v3/mediasoup-client/api)
|
* [mediasoup-client接口](https://mediasoup.org/documentation/v3/mediasoup-client/api)
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
按钮:选择房间、媒体服务、开关
|
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ const protocol = {
|
|||||||
* @returns 索引
|
* @returns 索引
|
||||||
*/
|
*/
|
||||||
buildId() {
|
buildId() {
|
||||||
if (++this.index > this.maxIndex) {
|
const me = this;
|
||||||
this.index = 0;
|
if (++me.index > me.maxIndex) {
|
||||||
|
me.index = 0;
|
||||||
}
|
}
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
return (
|
return (
|
||||||
@@ -30,8 +31,8 @@ const protocol = {
|
|||||||
1000000000000 * date.getHours() +
|
1000000000000 * date.getHours() +
|
||||||
10000000000 * date.getMinutes() +
|
10000000000 * date.getMinutes() +
|
||||||
100000000 * date.getSeconds() +
|
100000000 * date.getSeconds() +
|
||||||
1000 * this.clientIndex +
|
1000 * me.clientIndex +
|
||||||
this.index
|
me.index
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -2010,23 +2011,25 @@ class Taoyao extends RemoteClient {
|
|||||||
*/
|
*/
|
||||||
closeMedia() {
|
closeMedia() {
|
||||||
let me = this;
|
let me = this;
|
||||||
if (me.sendTransport) {
|
|
||||||
me.sendTransport.close();
|
|
||||||
}
|
|
||||||
if (me.recvTransport) {
|
|
||||||
me.recvTransport.close();
|
|
||||||
}
|
|
||||||
if(me.audioTrack) {
|
if(me.audioTrack) {
|
||||||
me.audioTrack.stop();
|
me.audioTrack.stop();
|
||||||
}
|
}
|
||||||
if(me.videoTrack) {
|
if(me.videoTrack) {
|
||||||
me.videoTrack.stop();
|
me.videoTrack.stop();
|
||||||
}
|
}
|
||||||
|
if (me.sendTransport) {
|
||||||
|
me.sendTransport.close();
|
||||||
|
}
|
||||||
|
if (me.recvTransport) {
|
||||||
|
me.recvTransport.close();
|
||||||
|
}
|
||||||
me.sendTransport = null;
|
me.sendTransport = null;
|
||||||
me.recvTransport = null;
|
me.recvTransport = null;
|
||||||
|
me.dataProducer = null;
|
||||||
me.audioProducer = null;
|
me.audioProducer = null;
|
||||||
me.videoProducer = null;
|
me.videoProducer = null;
|
||||||
me.dataProducer = null;
|
me.consumers.clear();
|
||||||
|
me.dataConsumers.clear();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 关闭资源
|
* 关闭资源
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
<outputDirectory>config</outputDirectory>
|
<outputDirectory>config</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>*.jks</include>
|
<include>*.jks</include>
|
||||||
|
<include>*.p12</include>
|
||||||
|
<include>*.pfx</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
<outputDirectory>config</outputDirectory>
|
<outputDirectory>config</outputDirectory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>*.jks</include>
|
<include>*.jks</include>
|
||||||
|
<include>*.p12</include>
|
||||||
|
<include>*.pfx</include>
|
||||||
</includes>
|
</includes>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
|
|||||||
@@ -175,7 +175,9 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<!-- Jar配置独立config目录 -->
|
<!-- Jar配置独立config目录 -->
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>*.jks</exclude>
|
<include>*.jks</include>
|
||||||
|
<include>*.p12</include>
|
||||||
|
<include>*.pfx</include>
|
||||||
<exclude>*.yml</exclude>
|
<exclude>*.yml</exclude>
|
||||||
<exclude>*.properties</exclude>
|
<exclude>*.properties</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.media.Content;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置
|
* 配置
|
||||||
@@ -24,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
@Tag(name = "配置", description = "配置管理")
|
@Tag(name = "配置", description = "配置管理")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/config")
|
@RequestMapping("/config")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
|
|
||||||
private final MediaProperties mediaProperties;
|
private final MediaProperties mediaProperties;
|
||||||
@@ -31,18 +33,6 @@ public class ConfigController {
|
|||||||
private final SocketProperties socketProperties;
|
private final SocketProperties socketProperties;
|
||||||
private final WebrtcProperties webrtcProperties;
|
private final WebrtcProperties webrtcProperties;
|
||||||
|
|
||||||
public ConfigController(
|
|
||||||
MediaProperties mediaProperties,
|
|
||||||
CameraProperties cameraProperties,
|
|
||||||
SocketProperties socketProperties,
|
|
||||||
WebrtcProperties webrtcProperties
|
|
||||||
) {
|
|
||||||
this.mediaProperties = mediaProperties;
|
|
||||||
this.cameraProperties = cameraProperties;
|
|
||||||
this.socketProperties = socketProperties;
|
|
||||||
this.webrtcProperties = webrtcProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "媒体配置", description = "媒体配置")
|
@Operation(summary = "媒体配置", description = "媒体配置")
|
||||||
@GetMapping("/media")
|
@GetMapping("/media")
|
||||||
@ApiResponse(content = @Content(schema = @Schema(implementation = MediaProperties.class)))
|
@ApiResponse(content = @Content(schema = @Schema(implementation = MediaProperties.class)))
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制
|
* 控制
|
||||||
@@ -37,6 +38,7 @@ import jakarta.validation.constraints.NotNull;
|
|||||||
@Validated
|
@Validated
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/control")
|
@RequestMapping("/control")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class ControlController {
|
public class ControlController {
|
||||||
|
|
||||||
private final ControlAiProtocol controlAiProtocol;
|
private final ControlAiProtocol controlAiProtocol;
|
||||||
@@ -49,28 +51,6 @@ public class ControlController {
|
|||||||
private final ControlConfigAudioProtocol controlConfigAudioProtocol;
|
private final ControlConfigAudioProtocol controlConfigAudioProtocol;
|
||||||
private final ControlConfigVideoProtocol controlConfigVideoProtocol;
|
private final ControlConfigVideoProtocol controlConfigVideoProtocol;
|
||||||
|
|
||||||
public ControlController(
|
|
||||||
ControlAiProtocol controlAiProtocol,
|
|
||||||
ControlPtzProtocol controlPtzProtocol,
|
|
||||||
ControlBellProtocol controlBellProtocol,
|
|
||||||
ControlBeautyProtocol controlBeautyProtocol,
|
|
||||||
ControlRecordProtocol controlRecordProtocol,
|
|
||||||
ControlWatermarkProtocol controlWatermarkProtocol,
|
|
||||||
ControlPhotographProtocol controlPhotographProtocol,
|
|
||||||
ControlConfigAudioProtocol controlConfigAudioProtocol,
|
|
||||||
ControlConfigVideoProtocol controlConfigVideoProtocol
|
|
||||||
) {
|
|
||||||
this.controlAiProtocol = controlAiProtocol;
|
|
||||||
this.controlPtzProtocol = controlPtzProtocol;
|
|
||||||
this.controlBellProtocol = controlBellProtocol;
|
|
||||||
this.controlBeautyProtocol = controlBeautyProtocol;
|
|
||||||
this.controlRecordProtocol = controlRecordProtocol;
|
|
||||||
this.controlWatermarkProtocol = controlWatermarkProtocol;
|
|
||||||
this.controlPhotographProtocol = controlPhotographProtocol;
|
|
||||||
this.controlConfigAudioProtocol = controlConfigAudioProtocol;
|
|
||||||
this.controlConfigVideoProtocol = controlConfigVideoProtocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "AI识别", description = "AI识别控制")
|
@Operation(summary = "AI识别", description = "AI识别控制")
|
||||||
@GetMapping("/ai/{clientId}")
|
@GetMapping("/ai/{clientId}")
|
||||||
public Message ai(@PathVariable String clientId, @Valid AiProperties aiProperties) {
|
public Message ai(@PathVariable String clientId, @Valid AiProperties aiProperties) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.boot.model.MessageCode;
|
||||||
import com.acgist.taoyao.signal.protocol.platform.PlatformRebootProtocol;
|
import com.acgist.taoyao.signal.protocol.platform.PlatformRebootProtocol;
|
||||||
import com.acgist.taoyao.signal.protocol.platform.PlatformShutdownProtocol;
|
import com.acgist.taoyao.signal.protocol.platform.PlatformShutdownProtocol;
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ public class PlatformController {
|
|||||||
@Operation(summary = "重启平台", description = "重启平台")
|
@Operation(summary = "重启平台", description = "重启平台")
|
||||||
@GetMapping("/reboot")
|
@GetMapping("/reboot")
|
||||||
public Message platformReboot() {
|
public Message platformReboot() {
|
||||||
|
if(this.platformRebootProtocol == null) {
|
||||||
|
return Message.fail(MessageCode.CODE_3406, "功能没有开启");
|
||||||
|
}
|
||||||
this.platformRebootProtocol.execute();
|
this.platformRebootProtocol.execute();
|
||||||
return Message.success();
|
return Message.success();
|
||||||
}
|
}
|
||||||
@@ -45,6 +49,9 @@ public class PlatformController {
|
|||||||
@Operation(summary = "关闭平台", description = "关闭平台")
|
@Operation(summary = "关闭平台", description = "关闭平台")
|
||||||
@GetMapping("/shutdown")
|
@GetMapping("/shutdown")
|
||||||
public Message platformShutdown() {
|
public Message platformShutdown() {
|
||||||
|
if(this.platformShutdownProtocol == null) {
|
||||||
|
return Message.fail(MessageCode.CODE_3406, "功能没有开启");
|
||||||
|
}
|
||||||
this.platformShutdownProtocol.execute();
|
this.platformShutdownProtocol.execute();
|
||||||
return Message.success();
|
return Message.success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
|
import com.acgist.taoyao.boot.model.MessageCode;
|
||||||
import com.acgist.taoyao.signal.protocol.system.SystemRebootProtocol;
|
import com.acgist.taoyao.signal.protocol.system.SystemRebootProtocol;
|
||||||
import com.acgist.taoyao.signal.protocol.system.SystemShutdownProtocol;
|
import com.acgist.taoyao.signal.protocol.system.SystemShutdownProtocol;
|
||||||
|
|
||||||
@@ -38,6 +39,9 @@ public class SystemController {
|
|||||||
@Operation(summary = "重启系统", description = "重启系统")
|
@Operation(summary = "重启系统", description = "重启系统")
|
||||||
@GetMapping("/reboot")
|
@GetMapping("/reboot")
|
||||||
public Message systemReboot() {
|
public Message systemReboot() {
|
||||||
|
if(this.systemRebootProtocol == null) {
|
||||||
|
return Message.fail(MessageCode.CODE_3406, "功能没有开启");
|
||||||
|
}
|
||||||
this.systemRebootProtocol.execute();
|
this.systemRebootProtocol.execute();
|
||||||
return Message.success();
|
return Message.success();
|
||||||
}
|
}
|
||||||
@@ -45,6 +49,9 @@ public class SystemController {
|
|||||||
@Operation(summary = "关闭系统", description = "关闭系统")
|
@Operation(summary = "关闭系统", description = "关闭系统")
|
||||||
@GetMapping("/shutdown")
|
@GetMapping("/shutdown")
|
||||||
public Message systemShutdown() {
|
public Message systemShutdown() {
|
||||||
|
if(this.systemShutdownProtocol == null) {
|
||||||
|
return Message.fail(MessageCode.CODE_3406, "功能没有开启");
|
||||||
|
}
|
||||||
this.systemShutdownProtocol.execute();
|
this.systemShutdownProtocol.execute();
|
||||||
return Message.success();
|
return Message.success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class SocketSignalTest {
|
|||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
// {"header":{"v":"1.0.0","id":1215310510002009,"signal":"room::enter"},"body":{"roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9"}}
|
// {"header":{"v":"1.0.0","id":1215310510002009,"signal":"room::enter"},"body":{"roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9"}}
|
||||||
// {"header":{"v":"1.0.0","id":1215310510002010,"signal":"media::transport::plain::in"},"body":{"roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9","rtcpMux":false,"comedia":true}}
|
// {"header":{"v":"1.0.0","id":1215310510002010,"signal":"media::transport::plain"},"body":{"roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9","rtcpMux":false,"comedia":true}}
|
||||||
// {"header":{"v":"1.0.0","id":1215375110006012,"signal":"media::produce"},"body":{"kind":"video","roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9","transportId":"14dc9307-bf9c-4442-a9ad-ce6a97623ef4","appData":{},"rtpParameters":{"codecs":[{"mimeType":"video/vp8","clockRate":90000,"payloadType":102,"rtcpFeedback":[]}],"encodings":[{"ssrc":123123}]}}}
|
// {"header":{"v":"1.0.0","id":1215375110006012,"signal":"media::produce"},"body":{"kind":"video","roomId":"8260e615-3081-4bfc-96a8-574f4dd780d9","transportId":"14dc9307-bf9c-4442-a9ad-ce6a97623ef4","appData":{},"rtpParameters":{"codecs":[{"mimeType":"video/vp8","clockRate":90000,"payloadType":102,"rtcpFeedback":[]}],"encodings":[{"ssrc":123123}]}}}
|
||||||
// ffplay -protocol_whitelist "file,udp,rtp" taoyao.sdp
|
// ffplay -protocol_whitelist "file,udp,rtp" taoyao.sdp
|
||||||
// ffmpeg -re -i video.mp4 -vcodec copy -map 0:0 -f rtp rtp://localhost:6666 > taoyao.sdp
|
// ffmpeg -re -i video.mp4 -vcodec copy -map 0:0 -f rtp rtp://localhost:6666 > taoyao.sdp
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.acgist.taoyao.signal.configuration;
|
package com.acgist.taoyao.signal.configuration;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
@@ -13,6 +12,7 @@ import com.acgist.taoyao.signal.event.EventPublisher;
|
|||||||
import com.acgist.taoyao.signal.protocol.ProtocolManager;
|
import com.acgist.taoyao.signal.protocol.ProtocolManager;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 信令自动配置
|
* 信令自动配置
|
||||||
@@ -20,13 +20,13 @@ import jakarta.annotation.PostConstruct;
|
|||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
|
@RequiredArgsConstructor
|
||||||
@EnableConfigurationProperties({
|
@EnableConfigurationProperties({
|
||||||
CameraProperties.class
|
CameraProperties.class
|
||||||
})
|
})
|
||||||
public class SignalAutoConfiguration {
|
public class SignalAutoConfiguration {
|
||||||
|
|
||||||
@Autowired
|
private final ApplicationContext applicationContext;
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.media.Content;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终端
|
* 终端
|
||||||
@@ -26,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
@Tag(name = "终端", description = "终端管理")
|
@Tag(name = "终端", description = "终端管理")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/client")
|
@RequestMapping("/client")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class ClientController {
|
public class ClientController {
|
||||||
|
|
||||||
private final ClientManager clientManager;
|
private final ClientManager clientManager;
|
||||||
@@ -33,16 +35,6 @@ public class ClientController {
|
|||||||
private final ClientRebootProtocol clientRebootProtocol;
|
private final ClientRebootProtocol clientRebootProtocol;
|
||||||
private final ClientShutdownProtocol clientShutdownProtocol;
|
private final ClientShutdownProtocol clientShutdownProtocol;
|
||||||
|
|
||||||
public ClientController(
|
|
||||||
ClientManager clientManager, ClientWakeupProtocol clientWakeupProtocol,
|
|
||||||
ClientRebootProtocol clientRebootProtocol, ClientShutdownProtocol clientShutdownProtocol
|
|
||||||
) {
|
|
||||||
this.clientManager = clientManager;
|
|
||||||
this.clientWakeupProtocol = clientWakeupProtocol;
|
|
||||||
this.clientRebootProtocol = clientRebootProtocol;
|
|
||||||
this.clientShutdownProtocol = clientShutdownProtocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "终端列表", description = "终端列表")
|
@Operation(summary = "终端列表", description = "终端列表")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiResponse(content = @Content(schema = @Schema(implementation = ClientStatus.class)))
|
@ApiResponse(content = @Content(schema = @Schema(implementation = ClientStatus.class)))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.acgist.taoyao.signal.protocol.Protocol;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,14 +27,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/protocol")
|
@RequestMapping("/protocol")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class ProtocolController {
|
public class ProtocolController {
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
public ProtocolController(ApplicationContext applicationContext) {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "信令列表", description = "信令列表Markdown")
|
@Operation(summary = "信令列表", description = "信令列表Markdown")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public String list() {
|
public String list() {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.media.Content;
|
|||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 房间
|
* 房间
|
||||||
@@ -29,14 +30,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
@Validated
|
@Validated
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/room")
|
@RequestMapping("/room")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class RoomController {
|
public class RoomController {
|
||||||
|
|
||||||
private final RoomManager roomManager;
|
private final RoomManager roomManager;
|
||||||
|
|
||||||
public RoomController(RoomManager roomManager) {
|
|
||||||
this.roomManager = roomManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "房间信息", description = "房间信息")
|
@Operation(summary = "房间信息", description = "房间信息")
|
||||||
@GetMapping("/log")
|
@GetMapping("/log")
|
||||||
public Message log() {
|
public Message log() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package com.acgist.taoyao.signal.party.media;
|
|||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
public enum RouteType {
|
public enum RouterType {
|
||||||
|
|
||||||
// 对讲:只有两个人之间的媒体相互路由
|
// 对讲:只有两个人之间的媒体相互路由
|
||||||
ONE_TO_ONE,
|
ONE_TO_ONE,
|
||||||
@@ -67,11 +67,16 @@ public class MediaConsumeProtocol extends ProtocolRoomAdapter implements Applica
|
|||||||
final ClientWrapper produceClientWrapper = producer.getProducerClient();
|
final ClientWrapper produceClientWrapper = producer.getProducerClient();
|
||||||
room.getClients().values().stream()
|
room.getClients().values().stream()
|
||||||
.filter(v -> v != produceClientWrapper)
|
.filter(v -> v != produceClientWrapper)
|
||||||
|
.filter(v -> v.getRecvTransport() != null)
|
||||||
.filter(v -> v.getSubscribeType().canConsume(producer))
|
.filter(v -> v.getSubscribeType().canConsume(producer))
|
||||||
.forEach(v -> this.consume(room, v, producer, this.build()));
|
.forEach(v -> this.consume(room, v, producer, this.build()));
|
||||||
} else if(event.getClientWrapper() != null) {
|
} else if(event.getClientWrapper() != null) {
|
||||||
// 创建WebRTC消费通道:消费其他终端
|
// 创建WebRTC消费通道:消费其他终端
|
||||||
final ClientWrapper consumeClientWrapper = event.getClientWrapper();
|
final ClientWrapper consumeClientWrapper = event.getClientWrapper();
|
||||||
|
if(consumeClientWrapper.getRecvTransport() == null) {
|
||||||
|
log.debug("没有消费通道:{}", consumeClientWrapper.getClientId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
room.getClients().values().stream()
|
room.getClients().values().stream()
|
||||||
.filter(v -> v != consumeClientWrapper)
|
.filter(v -> v != consumeClientWrapper)
|
||||||
.flatMap(v -> v.getProducers().values().stream())
|
.flatMap(v -> v.getProducers().values().stream())
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.acgist.taoyao.signal.protocol.media;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.Room;
|
||||||
|
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置路由类型信令
|
||||||
|
* 注意:不会添加移除消费者生产者,只会暂停恢复操作。
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
public class MediaSetRouterTypeProtocol extends ProtocolRoomAdapter {
|
||||||
|
|
||||||
|
public static final String SIGNAL = "media::set::router::type";
|
||||||
|
|
||||||
|
public MediaSetRouterTypeProtocol() {
|
||||||
|
super("设置路由类型信令", SIGNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
||||||
|
if(clientType.mediaClient()) {
|
||||||
|
// TODO:路由类型
|
||||||
|
} else {
|
||||||
|
this.logNoAdapter(clientType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.acgist.taoyao.signal.protocol.media;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建RTP输出通道信令
|
|
||||||
*
|
|
||||||
* @author acgist
|
|
||||||
*/
|
|
||||||
public class MediaTransportPlainOutProtocol {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,10 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建RTP输入通道信令
|
* 创建RTP输入通道信令
|
||||||
* TODO:srtp
|
* 注意:
|
||||||
|
* 3. ffmpeg不支持rtcpMux
|
||||||
|
* 2. comedia必须开启srtp功能
|
||||||
|
* 1. 如果关闭comedia不会自动升级双向通道
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@@ -34,14 +37,21 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
"roomId": "房间ID",
|
"roomId": "房间ID",
|
||||||
"rtcpMux": RTP和RTCP端口复用(true|false),
|
"rtcpMux": RTP和RTCP端口复用(true|false),
|
||||||
"comedia": 自动终端端口(true|false),
|
"comedia": 自动终端端口(true|false),
|
||||||
|
"enableSctp": 是否开启sctp(true|false),
|
||||||
|
"numSctpStreams": sctp数量,
|
||||||
|
"enableSrtp": 是否开启srtp(true|false),
|
||||||
|
"srtpCryptoSuite": {
|
||||||
|
"cryptoSuite": "算法(AEAD_AES_256_GCM|AEAD_AES_128_GCM|AES_CM_128_HMAC_SHA1_80|AES_CM_128_HMAC_SHA1_32)",
|
||||||
|
"keyBase64": "密钥"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
public class MediaTransportPlainInProtocol extends ProtocolRoomAdapter {
|
public class MediaTransportPlainProtocol extends ProtocolRoomAdapter {
|
||||||
|
|
||||||
public static final String SIGNAL = "media::transport::plain::in";
|
public static final String SIGNAL = "media::transport::plain";
|
||||||
|
|
||||||
public MediaTransportPlainInProtocol() {
|
public MediaTransportPlainProtocol() {
|
||||||
super("创建RTP输入通道信令", SIGNAL);
|
super("创建RTP输入通道信令", SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +75,7 @@ public class MediaTransportPlainInProtocol extends ProtocolRoomAdapter {
|
|||||||
log.warn("发送通道已经存在:{}", transportId);
|
log.warn("发送通道已经存在:{}", transportId);
|
||||||
}
|
}
|
||||||
clientWrapper.setSendTransport(sendTransport);
|
clientWrapper.setSendTransport(sendTransport);
|
||||||
|
// TODO:双向队列
|
||||||
// 拷贝属性
|
// 拷贝属性
|
||||||
sendTransport.copy(responseBody);
|
sendTransport.copy(responseBody);
|
||||||
client.push(response);
|
client.push(response);
|
||||||
@@ -40,6 +40,8 @@ public class RoomExpelProtocol extends ProtocolRoomAdapter {
|
|||||||
if(clientType.mediaClient()) {
|
if(clientType.mediaClient()) {
|
||||||
final String expelClientId = MapUtils.get(body, Constant.CLIENT_ID);
|
final String expelClientId = MapUtils.get(body, Constant.CLIENT_ID);
|
||||||
room.unicast(expelClientId, message);
|
room.unicast(expelClientId, message);
|
||||||
|
// 如果需要强制提出
|
||||||
|
// room.leave(this.clientManager.clients(expelClientId));
|
||||||
} else {
|
} else {
|
||||||
this.logNoAdapter(clientType);
|
this.logNoAdapter(clientType);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user