[+] 视频添加

This commit is contained in:
acgist
2023-02-26 16:44:30 +08:00
parent a0ebe8c842
commit 71ada0a8ca
10 changed files with 437 additions and 86 deletions

View File

@@ -573,7 +573,7 @@ class Signal {
} }
async mediaConsume(message, body) { async mediaConsume(message, body) {
const { roomId, clientId, streamId, producerId, transportId, rtpCapabilities } = body; const { roomId, clientId, sourceId, streamId, producerId, transportId, rtpCapabilities } = body;
const room = this.rooms.get(roomId); const room = this.rooms.get(roomId);
const producer = room.producers.get(producerId); const producer = room.producers.get(producerId);
const transport = room.transports.get(transportId); const transport = room.transports.get(transportId);
@@ -682,6 +682,7 @@ class Signal {
type: consumer.type, type: consumer.type,
roomId: roomId, roomId: roomId,
clientId: clientId, clientId: clientId,
sourceId: sourceId,
streamId: streamId, streamId: streamId,
producerId: producerId, producerId: producerId,
consumerId: consumer.id, consumerId: consumer.id,

View File

@@ -13,7 +13,8 @@
"vue": "^3.2.44", "vue": "^3.2.44",
"moment": "^2.29.4", "moment": "^2.29.4",
"element-plus": "^2.2.32", "element-plus": "^2.2.32",
"mediasoup-client": "^3.6.82" "mediasoup-client": "^3.6.82",
"@element-plus/icons": "^0.0.11"
}, },
"devDependencies": { "devDependencies": {
"vite": "^4.0.0", "vite": "^4.0.0",

View File

@@ -35,7 +35,7 @@
center center
width="30%" width="30%"
title="房间设置" title="房间设置"
@open="init" @open="loadList"
:show-close="false" :show-close="false"
v-model="roomVisible" v-model="roomVisible"
> >
@@ -80,22 +80,27 @@
</el-dialog> </el-dialog>
<!-- 菜单 --> <!-- 菜单 -->
<div class="menu"> <div class="menus">
<el-button type="primary" @click="signalVisible = true">连接信令</el-button> <el-button type="primary" @click="signalVisible = true">连接信令</el-button>
<el-button type="primary" @click="roomActive = 'enter'; roomVisible = true;">选择房间</el-button> <el-button type="primary" @click="roomActive = 'enter'; roomVisible = true;">选择房间</el-button>
<el-button type="primary" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button> <el-button type="primary" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button>
<el-button>邀请终端</el-button>
<el-button>退出房间</el-button> <el-button>退出房间</el-button>
<el-button type="danger">关闭房间</el-button> <el-button type="danger">关闭房间</el-button>
</div> </div>
<!-- 终端 --> <!-- 终端 -->
<div class="client"> <div class="clients">
<LocalClient ref="local"></LocalClient>
<RemoteClient :ref="'remote-' + kv[0]" v-for="(kv, index) in remoteClients" :key="index"></RemoteClient>
</div> </div>
</template> </template>
<script> <script>
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Taoyao } from "./components/Taoyao.js"; import { Taoyao } from "./components/Taoyao.js";
import LocalClient from './components/LocalClient.vue';
import RemoteClient from './components/RemoteClient.vue';
export default { export default {
name: "Taoyao", name: "Taoyao",
@@ -114,7 +119,8 @@ export default {
taoyao: {}, taoyao: {},
roomActive: "enter", roomActive: "enter",
roomVisible: false, roomVisible: false,
signalVisible: true, signalVisible: false,
remoteClients: new Map(),
}; };
}, },
mounted() { mounted() {
@@ -124,7 +130,14 @@ export default {
`); `);
}, },
methods: { methods: {
async init() { async connectSignal() {
let self = this;
self.taoyao = new Taoyao({ ...this.config });
self.remoteClients = self.taoyao.remoteClients;
await self.taoyao.connectSignal(self.callback, self.callbackMedia);
self.signalVisible = false;
},
async loadList() {
this.rooms = await this.taoyao.roomList(); this.rooms = await this.taoyao.roomList();
this.medias = await this.taoyao.mediaList(); this.medias = await this.taoyao.mediaList();
}, },
@@ -138,12 +151,6 @@ export default {
this.room = room; this.room = room;
await this.enterRoom(room.roomId); await this.enterRoom(room.roomId);
}, },
async connectSignal() {
let self = this;
self.taoyao = new Taoyao({ ...this.config });
await self.taoyao.connectSignal(self.callback);
self.signalVisible = false;
},
/** /**
* 信令回调 * 信令回调
* *
@@ -172,10 +179,37 @@ export default {
message: data.message, message: data.message,
type: "error", type: "error",
}); });
break; return true;
} }
return false; return false;
}, },
/**
* 媒体回调
*/
callbackMedia(type, track, consumer) {
const self = this;
return new Promise((resolve, reject) => {
if(type === 'local') {
self.$refs.local.media(track);
} else {
this.$refs['remote-' + consumer.sourceId][0].media(track, consumer);
}
resolve();
});
},
},
components: {
LocalClient,
RemoteClient
}, },
}; };
</script> </script>
<style>
.menus{width:100%;top:1rem;left:0;text-align:center;position:fixed;z-index:1;}
.clients{width:100%;height:100%;top:0;left:0;position:fixed;}
.client{float:left;width:50vw;height:50vh;border:1px solid #eee;}
.client .buttons{width:100%;bottom:1rem;left:0;text-align:center;position:absolute;padding:0.8rem 0;background: rgba(0,0,0,0.6);text-align:center;}
.client audio{display:none;}
.client video{width:100%;height:100%;}
</style>

View File

@@ -1,24 +1,113 @@
<!-- 本地终端 --> <!-- 本地终端 -->
<template></template> <template>
<div class="client">
<audio ref="audio"></audio>
<video ref="video"></video>
<div class="buttons">
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
<el-button type="primary" title="关闭摄像头" :icon="VideoPlay" circle />
<el-button title="交换媒体" :icon="Refresh" circle />
<el-button title="拍照" :icon="Camera" circle />
<el-button title="录像" :icon="VideoCamera" circle />
<el-button title="媒体信息" :icon="InfoFilled" circle />
<el-select placeholder="视频质量">
<el-option
v-for="option in options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</div>
</div>
</template>
<script> <script>
import { defineComponent } from "@vue/composition-api"; import {
Mute,
export default defineComponent({ Camera,
Refresh,
VideoPlay,
VideoPause,
InfoFilled,
Microphone,
VideoCamera,
CircleClose,
} from "@element-plus/icons";
export default {
name: "LocalClient",
setup() { setup() {
// 本地视频 return {
this._externalVideo = document.createElement("video"); Mute,
Camera,
this._externalVideo.controls = true; Refresh,
this._externalVideo.muted = true; VideoPlay,
this._externalVideo.loop = true; VideoPause,
this._externalVideo.setAttribute("playsinline", ""); InfoFilled,
this._externalVideo.src = EXTERNAL_VIDEO_SRC; Microphone,
VideoCamera,
// TODO关闭摄像头、视频、音频 CircleClose,
this._externalVideo };
.play()
.catch((error) => console.warn("externalVideo.play() failed:%o", error));
}, },
}); data() {
return {
taoyao: null,
audio: null,
video: null,
audioStream: null,
videoStream: null,
dataProducer: null,
audioProducer: null,
videoProducer: null,
options: [
{
value: "HD",
label: "高清",
},
{
value: "SD",
label: "标签",
},
{
value: "FD",
label: "超清",
},
{
value: "BD",
label: "蓝光",
},
{
value: "QD",
label: "2K",
},
{
value: "UD",
label: "4K",
},
],
};
},
mounted() {
this.audio = this.$refs.audio;
this.video = this.$refs.video;
},
methods: {
media(track) {
if (track.kind === "video") {
if (this.videoStream) {
// TODO资源释放
} else {
this.videoStream = new MediaStream();
this.videoStream.addTrack(track);
this.video.srcObject = this.videoStream;
}
this.video.play().catch((error) => console.warn("视频播放失败", error));
} else {
console.debug("本地不支持的媒体类型:", track);
}
},
},
};
</script> </script>

View File

@@ -1,14 +1,88 @@
<!-- 远程终端 --> <!-- 远程终端 -->
<template> <template>
<video></video> <div class="client">
<audio></audio> <audio ref="audio"></audio>
<div> <video ref="video"></video>
<el-button type="primary" title="关闭麦克风" :icon="Edit" circle /> <div class="buttons">
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
<el-button type="primary" title="关闭摄像头" :icon="VideoPlay" circle />
<el-button title="拍照" :icon="Camera" circle />
<el-button title="录像" :icon="VideoCamera" circle />
<el-button title="媒体信息" :icon="InfoFilled" circle />
<el-button title="踢出" :icon="CircleClose" circle />
</div>
</div> </div>
</template> </template>
<script> <script>
import {
Mute,
Camera,
Refresh,
VideoPlay,
VideoPause,
InfoFilled,
Microphone,
VideoCamera,
CircleClose,
} from "@element-plus/icons";
export default { export default {
name: "RemoteClient", name: "RemoteClient",
setup() {
return {
Mute,
Camera,
Refresh,
VideoPlay,
VideoPause,
InfoFilled,
Microphone,
VideoCamera,
CircleClose,
};
},
data() {
return {
taoyao: null,
audio: null,
video: null,
audioStream: null,
videoStream: null,
dataConsumer: null,
audioConsumer: null,
videoConsumer: null,
};
},
mounted() {
this.audio = this.$refs.audio;
this.video = this.$refs.video;
},
methods: {
media(track, consumer) {
if(track.kind === 'audio') {
if (this.audioStream) {
// TODO资源释放
} else {
this.audioStream = new MediaStream();
this.audioStream.addTrack(track);
this.audio.srcObject = this.audioStream;
} }
this.audio.play().catch((error) => console.warn("视频播放失败", error));
} else if(track.kind === 'video') {
if (this.videoStream) {
// TODO资源释放
} else {
this.videoStream = new MediaStream();
this.videoStream.addTrack(track);
this.video.srcObject = this.videoStream;
}
this.video.play().catch((error) => console.warn("视频播放失败", error));
} else {
}
}
}
};
</script> </script>

View File

@@ -11,18 +11,14 @@ import {
} from "./Config.js"; } from "./Config.js";
// Used for simulcast webcam video. // Used for simulcast webcam video.
const WEBCAM_SIMULCAST_ENCODINGS = const WEBCAM_SIMULCAST_ENCODINGS = [
[ { scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: "S1T2" },
{ scaleResolutionDownBy: 4, maxBitrate: 500000, scalabilityMode: 'S1T2' }, { scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: "S1T2" },
{ scaleResolutionDownBy: 2, maxBitrate: 1000000, scalabilityMode: 'S1T2' }, { scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: "S1T2" },
{ scaleResolutionDownBy: 1, maxBitrate: 5000000, scalabilityMode: 'S1T2' }
]; ];
// Used for VP9 webcam video. // Used for VP9 webcam video.
const WEBCAM_KSVC_ENCODINGS = const WEBCAM_KSVC_ENCODINGS = [{ scalabilityMode: "S3T3_KEY" }];
[
{ scalabilityMode: 'S3T3_KEY' }
];
/** /**
* 信令通道 * 信令通道
@@ -294,6 +290,12 @@ const signalChannel = {
case "client::shutdown": case "client::shutdown":
self.defaultClientShutdown(message); self.defaultClientShutdown(message);
break; break;
case "room::enter":
self.defaultRoomEnter(message);
break;
case "room::client::list":
self.defaultRoomClientList(message);
break;
case "platform::error": case "platform::error":
self.callbackError(message); self.callbackError(message);
break; break;
@@ -334,6 +336,24 @@ const signalChannel = {
console.info("关闭终端"); console.info("关闭终端");
window.close(); window.close();
}, },
defaultRoomEnter(message) {
const { roomId, clientId } = message.body;
if(clientId === this.taoyao.clientId) {
// 忽略自己
} else {
this.taoyao.remoteClients.set(clientId, roomId);
}
},
defaultRoomClientList(message) {
const self = this;
message.body.forEach(v => {
if(v.clientId === self.taoyao.clientId) {
// 忽略自己
} else {
self.taoyao.remoteClients.set(v.clientId, self.taoyao.roomId);
}
});
},
}; };
/** /**
@@ -354,6 +374,8 @@ class Taoyao {
password; password;
// 回调事件 // 回调事件
callback; callback;
// 媒体回调
callbackMedia;
// 音频媒体配置 // 音频媒体配置
audio; audio;
// 视频媒体配置 // 视频媒体配置
@@ -364,10 +386,6 @@ class Taoyao {
push; push;
// 请求信令 // 请求信令
request; request;
// 本地终端
localClient;
// 远程终端
remoteClients = new Map();
// 信令通道 // 信令通道
signalChannel; signalChannel;
// 发送媒体通道 // 发送媒体通道
@@ -381,30 +399,31 @@ class Taoyao {
// 是否生产 // 是否生产
produce; produce;
// 视频来源file | camera | screen // 视频来源file | camera | screen
videoSource = "screen"; videoSource = "camera";
// 强制使用TCP
forceTcp;
// 强制使用VP9 // 强制使用VP9
forceVP9; forceVP9;
// 强制使用H264 // 强制使用H264
forceH264; forceH264;
//
useSimulcast; useSimulcast;
// 是否生产数据
dataProduce;
// 是否生产音频 // 是否生产音频
audioProduce; audioProduce;
// 是否生成视频 // 是否生成视频
videoProduce; videoProduce;
// 强制使用TCP // 数据生产者
forceTcp; dataProducer;
// 使用数据通道
useDataChannel;
// 音频生产者 // 音频生产者
audioProducer; audioProducer;
// 视频生产者 // 视频生产者
videoProducer; videoProducer;
// 数据生产者 // 消费者:音频、视频、数据
dataChannnelProducer;
// 媒体消费者
consumers = new Map(); consumers = new Map();
// 数据消费者 // 远程终端
dataConsumers = new Map(); remoteClients = new Map();
constructor({ constructor({
roomId, roomId,
@@ -418,7 +437,7 @@ class Taoyao {
audioProduce = true, audioProduce = true,
videoProduce = true, videoProduce = true,
forceTcp = false, forceTcp = false,
useDataChannel = true, dataProduce = true,
}) { }) {
this.roomId = roomId; this.roomId = roomId;
this.clientId = clientId; this.clientId = clientId;
@@ -428,22 +447,24 @@ class Taoyao {
this.password = password; this.password = password;
this.consume = consume; this.consume = consume;
this.produce = produce; this.produce = produce;
this.dataProduce = produce && dataProduce;
this.audioProduce = produce && audioProduce; this.audioProduce = produce && audioProduce;
this.videoProduce = produce && videoProduce; this.videoProduce = produce && videoProduce;
this.forceTcp = forceTcp; this.forceTcp = forceTcp;
this.useDataChannel = useDataChannel;
} }
/** /**
* 连接信令 * 连接信令
* *
* @param {*} callback * @param {*} callback 信令回调
* @param {*} callbackMedia 媒体回调
* *
* @returns * @returns
*/ */
async connectSignal(callback) { async connectSignal(callback, callbackMedia) {
const self = this; const self = this;
self.callback = callback; self.callback = callback;
self.callbackMedia = callbackMedia;
self.signalChannel = signalChannel; self.signalChannel = signalChannel;
signalChannel.taoyao = self; signalChannel.taoyao = self;
// 不能直接this.push = this.signalChannel.push这样导致this对象错误 // 不能直接this.push = this.signalChannel.push这样导致this对象错误
@@ -469,6 +490,7 @@ class Taoyao {
} else { } else {
console.warn("没有注册回调:", message); console.warn("没有注册回调:", message);
} }
return;
} }
// 错误回调 // 错误回调
const errorMessage = protocol.buildMessage( const errorMessage = protocol.buildMessage(
@@ -490,6 +512,12 @@ class Taoyao {
); );
return response.body; return response.body;
} }
async clientList() {
const response = await this.request(
protocol.buildMessage("client::list", { roomId: self.roomId })
);
return response.body;
}
/** /**
* 创建房间 * 创建房间
*/ */
@@ -526,7 +554,7 @@ class Taoyao {
? self.mediasoupDevice.rtpCapabilities ? self.mediasoupDevice.rtpCapabilities
: undefined, : undefined,
sctpCapabilities: sctpCapabilities:
self.consume && self.useDataChannel self.consume && self.dataProduce
? self.mediasoupDevice.sctpCapabilities ? self.mediasoupDevice.sctpCapabilities
: undefined, : undefined,
}) })
@@ -564,7 +592,7 @@ class Taoyao {
forceTcp: self.forceTcp, forceTcp: self.forceTcp,
producing: true, producing: true,
consuming: false, consuming: false,
sctpCapabilities: self.useDataChannel sctpCapabilities: self.dataProduce
? self.mediasoupDevice.sctpCapabilities ? self.mediasoupDevice.sctpCapabilities
: undefined, : undefined,
}) })
@@ -661,7 +689,7 @@ class Taoyao {
forceTcp: self.forceTcp, forceTcp: self.forceTcp,
producing: false, producing: false,
consuming: true, consuming: true,
sctpCapabilities: self.useDataChannel sctpCapabilities: self.dataProduce
? self.mediasoupDevice.sctpCapabilities ? self.mediasoupDevice.sctpCapabilities
: undefined, : undefined,
}) })
@@ -843,20 +871,24 @@ class Taoyao {
track = stream.getVideoTracks()[0]; track = stream.getVideoTracks()[0];
} else if (self.videoSource === "screen") { } else if (self.videoSource === "screen") {
const stream = await navigator.mediaDevices.getDisplayMedia({ const stream = await navigator.mediaDevices.getDisplayMedia({
// 如果需要共享声音
audio: false, audio: false,
video: { video: {
displaySurface: "monitor",
logicalSurface: true,
cursor: true, cursor: true,
width: { max: 1920 }, width: { max: 1920 },
height: { max: 1080 }, height: { max: 1080 },
frameRate: { max: 30 }, frameRate: { max: 30 },
logicalSurface: true,
displaySurface: "monitor",
}, },
}); });
track = stream.getVideoTracks()[0]; track = stream.getVideoTracks()[0];
} else { } else {
// TODO异常 // TODO异常
} }
self.callbackMedia("local", track);
let codec; let codec;
let encodings; let encodings;
const codecOptions = { const codecOptions = {
@@ -924,6 +956,69 @@ class Taoyao {
} }
} }
async closeVideoProducer() {
console.debug("disableWebcam()");
if (!this.videoProducer) {
return;
}
this.videoProducer.close();
try {
await this.request(
protocol.buildMessage("media::producer::close", {
producerId: this.videoProducer.id,
})
);
} catch (error) {
console.error(error);
}
this._webcamProducer = null;
}
async pauseVideoProducer() {
console.debug("关闭摄像头");
this.videoProducer.pause();
try {
await this.request(
protocol.buildMessage("media::producer::pause", {
producerId: this.videoProducer.id,
})
);
} catch (error) {
console.error("关闭摄像头异常", error);
// TODO异常调用回调
}
}
async resumeVideoProducer() {
console.debug("恢复摄像头");
this.videoProducer.resume();
try {
await this.request(
protocol.buildMessage("media::producer::resume", {
producerId: this.videoProducer.id,
})
);
} catch (error) {
console.error("恢复摄像头异常", error);
}
}
async updateVideoConfig(config) {
console.debug("更新摄像头参数");
try {
this.videoProducer.track.stop();
// TODOscreen、参数配置
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
const track = stream.getVideoTracks()[0];
await this.videoProducer.replaceTrack({ track });
} catch (error) {
console.error("changeWebcam() | failed: %o", error);
}
}
/** /**
* 消费媒体 * 消费媒体
* *
@@ -941,6 +1036,7 @@ class Taoyao {
type, type,
roomId, roomId,
clientId, clientId,
sourceId,
streamId, streamId,
producerId, producerId,
consumerId, consumerId,
@@ -962,6 +1058,7 @@ class Taoyao {
appData, // Trick. appData, // Trick.
}); });
consumer.clientId = clientId; consumer.clientId = clientId;
consumer.sourceId = sourceId;
consumer.streamId = streamId; consumer.streamId = streamId;
self.consumers.set(consumer.id, consumer); self.consumers.set(consumer.id, consumer);
consumer.on("transportclose", () => { consumer.on("transportclose", () => {
@@ -991,17 +1088,9 @@ class Taoyao {
// ) // )
// ); // );
self.push(message); self.push(message);
console.log(consumer); console.log("消费者", consumer);
const audioElem = document.createElement("video"); self.callbackMedia("remote", consumer.track, consumer);
document.getElementsByTagName("body")[0].appendChild(audioElem);
const stream = new MediaStream();
stream.addTrack(consumer.track);
audioElem.srcObject = stream;
audioElem
.play()
.catch((error) => console.warn("audioElem.play() failed:%o", error));
// If audio-only mode is enabled, pause it. // If audio-only mode is enabled, pause it.
if (consumer.kind === "video" && !self.videoProduce) { if (consumer.kind === "video" && !self.videoProduce) {
@@ -1013,6 +1102,26 @@ class Taoyao {
} }
} }
async pauseConsumer(consumer) {
if (consumer.paused) return;
try {
await this._protoo.request("pauseConsumer", { consumerId: consumer.id });
consumer.pause();
} catch (error) {
logger.error("_pauseConsumer() | failed:%o", error);
}
}
async resumeConsumer(consumer) {
if (!consumer.paused) return;
try {
await this._protoo.request("resumeConsumer", { consumerId: consumer.id });
consumer.resume();
} catch (error) {
logger.error("_resumeConsumer() | failed:%o", error);
}
}
/** /**
* 验证设备 * 验证设备
*/ */

View File

@@ -163,6 +163,10 @@ public interface Constant {
* 终端ID * 终端ID
*/ */
String CLIENT_ID = "clientId"; String CLIENT_ID = "clientId";
/**
* 来源终端ID
*/
String SOURCE_ID = "sourceId";
/** /**
* 路由ID * 路由ID
*/ */

View File

@@ -105,6 +105,7 @@ public class MediaConsumeProtocol extends ProtocolRoomAdapter implements Applica
final String streamId = producer.getStreamId() + "->" + clientId; final String streamId = producer.getStreamId() + "->" + clientId;
body.put(Constant.ROOM_ID, room.getRoomId()); body.put(Constant.ROOM_ID, room.getRoomId());
body.put(Constant.CLIENT_ID, clientId); body.put(Constant.CLIENT_ID, clientId);
body.put(Constant.SOURCE_ID, producer.getProduceClient().getClientId());
body.put(Constant.STREAM_ID, streamId); body.put(Constant.STREAM_ID, streamId);
body.put(Constant.PRODUCER_ID, producer.getProducerId()); body.put(Constant.PRODUCER_ID, producer.getProducerId());
body.put(Constant.TRANSPORT_ID, recvTransport.getTransportId()); body.put(Constant.TRANSPORT_ID, recvTransport.getTransportId());

View File

@@ -1,5 +1,36 @@
package com.acgist.taoyao.signal.protocol.room; package com.acgist.taoyao.signal.protocol.room;
public class RoomClientListProtocol { 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.flute.media.Room;
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
/**
* 房间终端列表信令
*
* @author acgist
*/
@Protocol
@Description(
flow = "终端=>信令服务->终端"
)
public class RoomClientListProtocol extends ProtocolRoomAdapter {
public static final String SIGNAL = "room::client::list";
public RoomClientListProtocol() {
super("房间终端列表信令", SIGNAL);
}
@Override
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
message.setBody(room.clientStatus());
client.push(message);
}
} }

View File

@@ -45,8 +45,11 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter {
public static final String SIGNAL = "room::enter"; public static final String SIGNAL = "room::enter";
public RoomEnterProtocol() { private final RoomClientListProtocol roomClientListProtocol;
public RoomEnterProtocol(RoomClientListProtocol roomClientListProtocol) {
super("进入房间信令", SIGNAL); super("进入房间信令", SIGNAL);
this.roomClientListProtocol = roomClientListProtocol;
} }
@Override @Override
@@ -71,6 +74,10 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter {
)); ));
room.broadcast(message); room.broadcast(message);
log.info("进入房间:{} - {}", clientId, room.getRoomId()); log.info("进入房间:{} - {}", clientId, room.getRoomId());
// 推送房间用户信息
final Message roomClientListMessage = this.roomClientListProtocol.build();
roomClientListMessage.setBody(room.clientStatus());
client.push(roomClientListMessage);
} }
} }