[*] 修复通道删除

This commit is contained in:
acgist
2023-06-02 09:20:40 +08:00
parent becc68b05f
commit d49444ba60
9 changed files with 96 additions and 74 deletions

View File

@@ -363,50 +363,48 @@ public final class Taoyao implements ITaoyao {
short messageLength = 0; short messageLength = 0;
final byte[] bytes = new byte[1024]; final byte[] bytes = new byte[1024];
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while (!this.close && this.connect) { try {
try { while (!this.close && this.connect && (length = this.input.read(bytes)) >= 0) {
while (!this.close && (length = this.input.read(bytes)) >= 0) { buffer.put(bytes, 0, length);
buffer.put(bytes, 0, length); while (buffer.position() > 0) {
while (buffer.position() > 0) { if (messageLength <= 0) {
if (messageLength <= 0) { if (buffer.position() < Short.BYTES) {
if (buffer.position() < Short.BYTES) { // 不够消息长度
// 不够消息长度 break;
break;
} else {
buffer.flip();
messageLength = buffer.getShort();
buffer.compact();
if (messageLength > 16 * 1024) {
throw new RuntimeException("超过最大数据大小:" + messageLength);
}
}
} else { } else {
if (buffer.position() < messageLength) { buffer.flip();
// 不够消息长度 messageLength = buffer.getShort();
break; buffer.compact();
} else { if (messageLength > 16 * 1024) {
final byte[] message = new byte[messageLength]; throw new RuntimeException("超过最大数据大小:" + messageLength);
messageLength = 0; }
buffer.flip(); }
buffer.get(message); } else {
buffer.compact(); if (buffer.position() < messageLength) {
final String content = new String(this.decrypt.doFinal(message)); // 不够消息长度
try { break;
this.on(content); } else {
} catch (Exception e) { final byte[] message = new byte[messageLength];
Log.e(Taoyao.class.getSimpleName(), "处理信令异常:" + content, e); messageLength = 0;
this.taoyaoListener.onError(e); buffer.flip();
} buffer.get(message);
buffer.compact();
final String content = new String(this.decrypt.doFinal(message));
try {
this.on(content);
} catch (Exception e) {
Log.e(Taoyao.class.getSimpleName(), "处理信令异常:" + content, e);
this.taoyaoListener.onError(e);
} }
} }
} }
} }
} catch (Exception e) {
Log.e(Taoyao.class.getSimpleName(), "接收信令异常", e);
this.disconnect();
} }
} catch (Exception e) {
Log.e(Taoyao.class.getSimpleName(), "接收信令异常", e);
this.disconnect();
} }
if (!this.close && !this.connect) { if (!this.close) {
this.messageHandler.post(this::connect); this.messageHandler.post(this::connect);
} }
} }
@@ -439,7 +437,11 @@ public final class Taoyao implements ITaoyao {
*/ */
@Override @Override
public void push(Message message) { public void push(Message message) {
if (this.output == null) { if (this.close) {
Log.w(Taoyao.class.getSimpleName(), "通道已经关闭:" + message);
return;
}
if (!this.connect) {
Log.w(Taoyao.class.getSimpleName(), "通道没有打开:" + message); Log.w(Taoyao.class.getSimpleName(), "通道没有打开:" + message);
return; return;
} }
@@ -448,6 +450,7 @@ public final class Taoyao implements ITaoyao {
this.output.write(this.encrypt(message)); this.output.write(this.encrypt(message));
} catch (Exception e) { } catch (Exception e) {
Log.e(Taoyao.class.getSimpleName(), "请求信令异常:" + message, e); Log.e(Taoyao.class.getSimpleName(), "请求信令异常:" + message, e);
this.disconnect();
} }
} }

View File

@@ -792,13 +792,15 @@ class Taoyao {
const { roomId, clientId, host, audioPort, videoPort, rtpCapabilities, audioStreamId, videoStreamId, audioProducerId, videoProducerId } = body; const { roomId, clientId, host, audioPort, videoPort, rtpCapabilities, audioStreamId, videoStreamId, audioProducerId, videoProducerId } = body;
const plainTransportOptions = { const plainTransportOptions = {
...config.mediasoup.plainTransportOptions, ...config.mediasoup.plainTransportOptions,
rtcpMux : true, rtcpMux: false,
comedia : true comedia: false
}; };
let videoConsumerId; let videoConsumerId;
let audioConsumerId; let audioConsumerId;
let audioTransportId; let audioTransportId;
let videoTransportId; let videoTransportId;
let audioRtpParameters;
let videoRtpParameters;
if(audioProducerId) { if(audioProducerId) {
const audioTransport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions); const audioTransport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions);
audioTransportId = audioTransport.id; audioTransportId = audioTransport.id;
@@ -810,9 +812,9 @@ class Taoyao {
room.transports.delete(audioTransport.id) room.transports.delete(audioTransport.id)
}); });
await audioTransport.connect({ await audioTransport.connect({
ip : host, ip : host,
port : audioPort, port : audioPort,
rtcpPort : audioPort rtcpPort: audioPort
}); });
const audioConsumer = await audioTransport.consume({ const audioConsumer = await audioTransport.consume({
producerId: audioProducerId, producerId: audioProducerId,
@@ -820,6 +822,7 @@ class Taoyao {
paused: true paused: true
}); });
audioConsumerId = audioConsumer.id; audioConsumerId = audioConsumer.id;
audioRtpParameters = audioConsumer.rtpParameters;
await audioConsumer.resume(); await audioConsumer.resume();
audioConsumer.clientId = clientId; audioConsumer.clientId = clientId;
audioConsumer.streamId = audioStreamId; audioConsumer.streamId = audioStreamId;
@@ -828,7 +831,7 @@ class Taoyao {
console.log("controlServerRecord audioConsumer close", audioConsumer.id); console.log("controlServerRecord audioConsumer close", audioConsumer.id);
room.consumers.delete(audioConsumer.id); room.consumers.delete(audioConsumer.id);
}); });
console.log("controlServerRecord audio", audioTransportId, audioConsumerId, audioTransport.tuple); console.log("controlServerRecord audio", audioTransportId, audioConsumerId, audioTransport.tuple, audioRtpParameters);
} }
if(videoProducerId) { if(videoProducerId) {
const videoTransport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions); const videoTransport = await room.mediasoupRouter.createPlainTransport(plainTransportOptions);
@@ -841,9 +844,9 @@ class Taoyao {
room.transports.delete(videoTransport.id) room.transports.delete(videoTransport.id)
}); });
await videoTransport.connect({ await videoTransport.connect({
ip : host, ip : host,
port : videoPort, port : videoPort,
rtcpPort : videoPort rtcpPort: videoPort
}); });
const videoConsumer = await videoTransport.consume({ const videoConsumer = await videoTransport.consume({
producerId: videoProducerId, producerId: videoProducerId,
@@ -851,6 +854,7 @@ class Taoyao {
paused: true paused: true
}); });
videoConsumerId = videoConsumer.id; videoConsumerId = videoConsumer.id;
videoRtpParameters = videoConsumer.rtpParameters;
await videoConsumer.resume(); await videoConsumer.resume();
videoConsumer.clientId = clientId; videoConsumer.clientId = clientId;
videoConsumer.streamId = videoStreamId; videoConsumer.streamId = videoStreamId;
@@ -859,14 +863,16 @@ class Taoyao {
console.log("controlServerRecord videoConsumer close", videoConsumer.id); console.log("controlServerRecord videoConsumer close", videoConsumer.id);
room.consumers.delete(videoConsumer.id); room.consumers.delete(videoConsumer.id);
}); });
console.log("controlServerRecord video", videoTransportId, videoConsumerId, videoTransport.tuple); console.log("controlServerRecord video", videoTransportId, videoConsumerId, videoTransport.tuple, videoRtpParameters);
} }
message.body = { message.body = {
roomId : roomId, roomId : roomId,
audioConsumerId : audioConsumerId, audioConsumerId : audioConsumerId,
videoConsumerId : videoConsumerId, videoConsumerId : videoConsumerId,
audioTransportId : audioTransportId, audioTransportId : audioTransportId,
videoTransportId : videoTransportId, videoTransportId : videoTransportId,
audioRtpParameters: audioRtpParameters,
videoRtpParameters: videoRtpParameters,
}; };
me.push(message); me.push(message);
} }
@@ -1528,6 +1534,11 @@ class Taoyao {
*/ */
transportEvent(type, roomId, transport) { transportEvent(type, roomId, transport) {
const self = this; const self = this;
const room = self.rooms.get(roomId);
if(!room) {
// TODO提示
return;
}
/********************* 通用通道事件 *********************/ /********************* 通用通道事件 *********************/
transport.on("routerclose", () => { transport.on("routerclose", () => {
console.info("transport routerclose", transport.id); console.info("transport routerclose", transport.id);

View File

@@ -10,8 +10,8 @@
<el-button @click="taoyao.mediaProducerResume(videoProducer.id)" v-show="videoProducer && videoProducer.paused" type="danger" title="打开摄像头" :icon="VideoPlay" circle /> <el-button @click="taoyao.mediaProducerResume(videoProducer.id)" v-show="videoProducer && videoProducer.paused" type="danger" title="打开摄像头" :icon="VideoPlay" circle />
<el-button @click="taoyao.mediaProducerPause(videoProducer.id)" v-show="videoProducer && !videoProducer.paused" type="primary" title="关闭摄像头" :icon="VideoPause" circle /> <el-button @click="taoyao.mediaProducerPause(videoProducer.id)" v-show="videoProducer && !videoProducer.paused" type="primary" title="关闭摄像头" :icon="VideoPause" circle />
<el-button @click="exchangeVideoSource" :icon="Refresh" circle title="交换媒体" /> <el-button @click="exchangeVideoSource" :icon="Refresh" circle title="交换媒体" />
<el-button @onclick="localPhotograph" :icon="Camera" circle title="拍照" /> <el-button @click="localPhotograph" :icon="Camera" circle title="拍照" />
<el-button @onclick="localClientRecord" :icon="VideoCamera" circle title="录像" :type="clientRecord ? 'danger' : ''" /> <el-button @click="localClientRecord" :icon="VideoCamera" circle title="录像" :type="clientRecord ? 'danger' : ''" />
<el-button @click="taoyao.controlServerRecord(client.clientId, (serverRecord = !serverRecord))" :icon="MostlyCloudy" circle title="录像" :type="serverRecord ? 'danger' : ''" /> <el-button @click="taoyao.controlServerRecord(client.clientId, (serverRecord = !serverRecord))" :icon="MostlyCloudy" circle title="录像" :type="serverRecord ? 'danger' : ''" />
<el-button @click="taoyao.mediaProducerStatus()" :icon="InfoFilled" circle title="媒体信息" /> <el-button @click="taoyao.mediaProducerStatus()" :icon="InfoFilled" circle title="媒体信息" />
<el-popover placement="top" :width="240" trigger="hover"> <el-popover placement="top" :width="240" trigger="hover">

View File

@@ -1958,7 +1958,7 @@ class Taoyao extends RemoteClient {
let track; let track;
try { try {
console.debug("打开麦克风"); console.debug("打开麦克风");
let track = self.getAudioTrack(); let track = await self.getAudioTrack();
this.audioProducer = await this.sendTransport.produce({ this.audioProducer = await this.sendTransport.produce({
track, track,
codecOptions: { codecOptions: {
@@ -2528,7 +2528,7 @@ class Taoyao extends RemoteClient {
videoBitsPerSecond: 2400 * 1000, videoBitsPerSecond: 2400 * 1000,
mimeType: 'video/webm;codecs=opus,h264', mimeType: 'video/webm;codecs=opus,h264',
}); });
mediaRecorder.onstop = function (e) { me.mediaRecorder.onstop = function (e) {
const blob = new Blob(me.mediaRecorderChunks); const blob = new Blob(me.mediaRecorderChunks);
const objectURL = URL.createObjectURL(blob); const objectURL = URL.createObjectURL(blob);
const download = document.createElement('a'); const download = document.createElement('a');
@@ -2541,7 +2541,7 @@ class Taoyao extends RemoteClient {
URL.revokeObjectURL(objectURL); URL.revokeObjectURL(objectURL);
me.mediaRecorderChunks = []; me.mediaRecorderChunks = [];
}; };
mediaRecorder.ondataavailable = (e) => { me.mediaRecorder.ondataavailable = (e) => {
me.mediaRecorderChunks.push(e.data); me.mediaRecorderChunks.push(e.data);
}; };
me.mediaRecorder.start(); me.mediaRecorder.start();

View File

@@ -31,6 +31,10 @@ public class FfmpegProperties {
private String storageImagePath; private String storageImagePath;
@Schema(title = "视频存储目录", description = "视频存储目录") @Schema(title = "视频存储目录", description = "视频存储目录")
private String storageVideoPath; private String storageVideoPath;
@Schema(title = "预览截图时间", description = "预览截图时间")
private Integer previewTime;
@Schema(title = "预览截图时间", description = "预览截图时间")
private String durationRegex;
@Schema(title = "录像录像地址", description = "录像录像地址") @Schema(title = "录像录像地址", description = "录像录像地址")
private String host; private String host;
@Schema(title = "录像最小端口", description = "录像最小端口") @Schema(title = "录像最小端口", description = "录像最小端口")

View File

@@ -235,13 +235,12 @@ taoyao:
c=IN IP4 127.0.0.1 c=IN IP4 127.0.0.1
a=rtpmap:100 OPUS/48000/2 a=rtpmap:100 OPUS/48000/2
a=fmtp:100 sprop-stereo=1 a=fmtp:100 sprop-stereo=1
m=video %d RTP/AVP 107 m=video %d RTP/AVP 101
c=IN IP4 127.0.0.1 c=IN IP4 127.0.0.1
a=rtpmap:107 H264/90000 a=rtpmap:101 VP8/90000
a=fmtp:107 packetization-mode=1
# 录像命令 # 录像命令
record: ffmpeg -protocol_whitelist "file,rtp,udp" -y -i %s %s record: ffmpeg -protocol_whitelist "file,rtp,udp" -y -i %s %s
# 截图命令 # 预览命令
preview: ffmpeg -y -i %s -ss %d -vframes 1 -f image2 %s preview: ffmpeg -y -i %s -ss %d -vframes 1 -f image2 %s
# 时长命令 # 时长命令
duration: ffprobe -i %s -show_entries format=duration duration: ffprobe -i %s -show_entries format=duration
@@ -251,6 +250,10 @@ taoyao:
storage-image-path: /data/taoyao/storage/image storage-image-path: /data/taoyao/storage/image
# 视频存储目录 # 视频存储目录
storage-video-path: /data/taoyao/storage/video storage-video-path: /data/taoyao/storage/video
# 预览时间
preview-time: 4
# 时长提取
duration-regex: .*duration\=([0-9\.]+).*
# 录像地址 # 录像地址
#host: 127.0.0.1 #host: 127.0.0.1
host: 192.168.8.40 host: 192.168.8.40

View File

@@ -16,29 +16,28 @@ public class RecorderTest {
ffmpegProperties.setHost("127.0.0.1"); ffmpegProperties.setHost("127.0.0.1");
ffmpegProperties.setSdp(""" ffmpegProperties.setSdp("""
v=0 v=0
o=- 0 0 IN IP4 %s o=- 0 0 IN IP4 127.0.0.1
s=TaoyaoRecord s=TaoyaoRecord
t=0 0 t=0 0
m=audio %d RTP/AVP 97 m=audio %d RTP/AVP 97
c=IN IP4 %s c=IN IP4 127.0.0.1
a=rtpmap:97 OPUS/48000/2 a=rtpmap:97 OPUS/48000/2
a=fmtp:97 sprop-stereo=1 a=fmtp:97 sprop-stereo=1
m=video %d RTP/AVP 96 m=video %d RTP/AVP 96
c=IN IP4 %s c=IN IP4 127.0.0.1
a=rtpmap:96 VP8/90000 a=rtpmap:96 VP8/90000
a=fmtp:96 packetization-mode=1
"""); """);
// ffmpegProperties.setSdp(""" // ffmpegProperties.setSdp("""
// v=0 // v=0
// o=- 0 0 IN IP4 %s // o=- 0 0 IN IP4 127.0.0.1
// s=TaoyaoRecord // s=TaoyaoRecord
// t=0 0 // t=0 0
// m=audio %d RTP/AVP 97 // m=audio %d RTP/AVP 97
// c=IN IP4 %s // c=IN IP4 127.0.0.1
// a=rtpmap:97 OPUS/48000/2 // a=rtpmap:97 OPUS/48000/2
// a=fmtp:97 sprop-stereo=1 // a=fmtp:97 sprop-stereo=1
// m=video %d RTP/AVP 96 // m=video %d RTP/AVP 96
// c=IN IP4 %s // c=IN IP4 127.0.0.1
// a=rtpmap:96 H264/90000 // a=rtpmap:96 H264/90000
// a=fmtp:96 packetization-mode=1 // a=fmtp:96 packetization-mode=1
// """); // """);

View File

@@ -159,6 +159,7 @@ public class Recorder {
this.thread.setDaemon(true); this.thread.setDaemon(true);
this.thread.setName("TaoyaoRecord"); this.thread.setName("TaoyaoRecord");
this.thread.start(); this.thread.start();
log.info("开始录像:{}", this.folder);
} }
/** /**
@@ -210,14 +211,14 @@ public class Recorder {
* 视频预览截图 * 视频预览截图
*/ */
private void preview() { private void preview() {
int time = 2; int time = this.ffmpegProperties.getPreviewTime();
final File file = Paths.get(this.preview).toFile(); final File file = Paths.get(this.preview).toFile();
while(time > 0 && !(file.exists() && file.length() > 0L)) { do {
log.debug("视频预览截图{}", this.preview); log.debug("视频预览:{}", this.preview);
final String previewScript = String.format(this.ffmpegProperties.getPreview(), this.filepath, time, this.preview); final String previewScript = String.format(this.ffmpegProperties.getPreview(), this.filepath, time, this.preview);
ScriptUtils.execute(previewScript); ScriptUtils.execute(previewScript);
time /= 2; time /= 2;
} } while (time > 0 && !(file.exists() && file.length() > 0L));
} }
/** /**
@@ -227,7 +228,7 @@ public class Recorder {
log.debug("视频时长:{}", this.filepath); log.debug("视频时长:{}", this.filepath);
final String durationScript = String.format(this.ffmpegProperties.getDuration(), this.filepath); final String durationScript = String.format(this.ffmpegProperties.getDuration(), this.filepath);
final ScriptExecutor executor = ScriptUtils.execute(durationScript); final ScriptExecutor executor = ScriptUtils.execute(durationScript);
final Pattern pattern = Pattern.compile(".*duration\\=([0-9\\.]+).*"); final Pattern pattern = Pattern.compile(this.ffmpegProperties.getDurationRegex());
final Matcher matcher = pattern.matcher(executor.getResult()); final Matcher matcher = pattern.matcher(executor.getResult());
String duration = null; String duration = null;
if(matcher.find()) { if(matcher.find()) {

View File

@@ -148,6 +148,7 @@ public class ControlServerRecordProtocol extends ProtocolControlAdapter implemen
message.setBody(body); message.setBody(body);
final Client mediaClient = room.getMediaClient(); final Client mediaClient = room.getMediaClient();
mediaClient.request(message); mediaClient.request(message);
// TODO回写ID格式自动判断
return recorder.getFilepath(); return recorder.getFilepath();
} }