[+] 音频监控
This commit is contained in:
@@ -49,3 +49,7 @@
|
|||||||
* 视频:水印、美颜、AI识别
|
* 视频:水印、美颜、AI识别
|
||||||
* P2P
|
* P2P
|
||||||
* 反复测试推流拉流、拉人踢人、音频视频控制
|
* 反复测试推流拉流、拉人踢人、音频视频控制
|
||||||
|
* 优化JS错误回调 -> platform::error
|
||||||
|
* 24小时不关闭媒体/一秒一次推拉流十分钟测试/三十秒推拉流一小时测试
|
||||||
|
* 标识 -> ID
|
||||||
|
* 所有字段获取 -> get
|
||||||
@@ -211,6 +211,7 @@ const signalChannel = {
|
|||||||
clearTimeout(me.reconnectTimer);
|
clearTimeout(me.reconnectTimer);
|
||||||
me.reconnection = false;
|
me.reconnection = false;
|
||||||
me.channel.close();
|
me.channel.close();
|
||||||
|
me.taoyao.connect = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -228,9 +229,9 @@ class Room {
|
|||||||
webRtcServer = null;
|
webRtcServer = null;
|
||||||
// 路由
|
// 路由
|
||||||
mediasoupRouter = null;
|
mediasoupRouter = null;
|
||||||
// 音频监控
|
// 音量监控
|
||||||
audioLevelObserver = null;
|
audioLevelObserver = null;
|
||||||
// 音频监控
|
// 采样监控
|
||||||
activeSpeakerObserver = null;
|
activeSpeakerObserver = null;
|
||||||
// 消费者复制数量
|
// 消费者复制数量
|
||||||
consumerReplicas = 0;
|
consumerReplicas = 0;
|
||||||
@@ -266,24 +267,22 @@ class Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 声音监控
|
* 音量监控
|
||||||
*/
|
*/
|
||||||
handleAudioLevelObserver() {
|
handleAudioLevelObserver() {
|
||||||
const self = this;
|
const self = this;
|
||||||
// 声音
|
|
||||||
self.audioLevelObserver.on("volumes", (volumes) => {
|
self.audioLevelObserver.on("volumes", (volumes) => {
|
||||||
for (const value of volumes) {
|
for (const value of volumes) {
|
||||||
const { producer, volume } = value;
|
const { producer, volume } = value;
|
||||||
signalChannel.push(
|
signalChannel.push(
|
||||||
protocol.buildMessage("media::audio::active::speaker", {
|
protocol.buildMessage("media::audio::active::speaker", {
|
||||||
|
volume: volume,
|
||||||
roomId: self.roomId,
|
roomId: self.roomId,
|
||||||
clientId: producer.clientId,
|
clientId: producer.clientId,
|
||||||
volume: volume,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 静音
|
|
||||||
self.audioLevelObserver.on("silence", () => {
|
self.audioLevelObserver.on("silence", () => {
|
||||||
signalChannel.push(
|
signalChannel.push(
|
||||||
protocol.buildMessage("media::audio::active::speaker", {
|
protocol.buildMessage("media::audio::active::speaker", {
|
||||||
@@ -294,7 +293,7 @@ class Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 说话监控
|
* 采样监控
|
||||||
*/
|
*/
|
||||||
handleActiveSpeakerObserver() {
|
handleActiveSpeakerObserver() {
|
||||||
const self = this;
|
const self = this;
|
||||||
@@ -332,6 +331,8 @@ class Room {
|
|||||||
// me.consumers.forEach(v => v.close());
|
// me.consumers.forEach(v => v.close());
|
||||||
// me.dataProducers.forEach(v => v.close());
|
// me.dataProducers.forEach(v => v.close());
|
||||||
// me.dataConsumers.forEach(v => v.close());
|
// me.dataConsumers.forEach(v => v.close());
|
||||||
|
me.audioLevelObserver.close();
|
||||||
|
me.activeSpeakerObserver.close();
|
||||||
me.transports.forEach(v => v.close());
|
me.transports.forEach(v => v.close());
|
||||||
me.mediasoupRouter.close();
|
me.mediasoupRouter.close();
|
||||||
}
|
}
|
||||||
@@ -404,6 +405,9 @@ class Taoyao {
|
|||||||
case "media::transport::webrtc::create":
|
case "media::transport::webrtc::create":
|
||||||
this.mediaTransportWebrtcCreate(message, body);
|
this.mediaTransportWebrtcCreate(message, body);
|
||||||
break;
|
break;
|
||||||
|
case "platform::error":
|
||||||
|
this.platformError(message, body);
|
||||||
|
break;
|
||||||
case "room::create":
|
case "room::create":
|
||||||
this.roomCreate(message, body);
|
this.roomCreate(message, body);
|
||||||
break;
|
break;
|
||||||
@@ -737,7 +741,7 @@ class Taoyao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由RTP能力信令
|
* 路由RTP协商信令
|
||||||
*
|
*
|
||||||
* @param {*} message 消息
|
* @param {*} message 消息
|
||||||
* @param {*} body 消息主体
|
* @param {*} body 消息主体
|
||||||
@@ -848,7 +852,18 @@ class Taoyao {
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 平台异常信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
* @param {*} body 消息主体
|
||||||
|
*/
|
||||||
|
platformError(message, body) {
|
||||||
|
const { code } = message;
|
||||||
|
if(code === "3401") {
|
||||||
|
signalChannel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 关闭房间信令
|
* 关闭房间信令
|
||||||
*
|
*
|
||||||
@@ -885,14 +900,16 @@ class Taoyao {
|
|||||||
const mediasoupWorker = me.nextMediasoupWorker();
|
const mediasoupWorker = me.nextMediasoupWorker();
|
||||||
const { mediaCodecs } = config.mediasoup.routerOptions;
|
const { mediaCodecs } = config.mediasoup.routerOptions;
|
||||||
const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs });
|
const mediasoupRouter = await mediasoupWorker.createRouter({ mediaCodecs });
|
||||||
// TODO:下面两个监控改为配置启用
|
// 音量监控
|
||||||
const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver({
|
const audioLevelObserver = await mediasoupRouter.createAudioLevelObserver({
|
||||||
maxEntries: 1,
|
|
||||||
threshold: -80,
|
|
||||||
interval: 2000,
|
interval: 2000,
|
||||||
|
// 范围:-127~0
|
||||||
|
threshold: -80,
|
||||||
|
// 采样数量
|
||||||
|
maxEntries: 2,
|
||||||
});
|
});
|
||||||
const activeSpeakerObserver =
|
// 采样监控
|
||||||
await mediasoupRouter.createActiveSpeakerObserver({
|
const activeSpeakerObserver = await mediasoupRouter.createActiveSpeakerObserver({
|
||||||
interval: 500,
|
interval: 500,
|
||||||
});
|
});
|
||||||
room = new Room({
|
room = new Room({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!-- 桃夭 -->
|
<!-- 桃夭 -->
|
||||||
<template>
|
<template>
|
||||||
|
<div id="taoyao">
|
||||||
<!-- 信令 -->
|
<!-- 信令 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
center
|
center
|
||||||
@@ -78,25 +79,26 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary" @click="enterRoom" v-if="roomActive === 'enter'">进入</el-button>
|
<el-button type="primary" @click="roomEnter" v-if="roomActive === 'enter'">进入</el-button>
|
||||||
<el-button type="primary" @click="roomCreate" v-if="roomActive === 'create'">创建</el-button>
|
<el-button type="primary" @click="roomCreate" v-if="roomActive === 'create'">创建</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 菜单 -->
|
<!-- 菜单 -->
|
||||||
<div class="menus">
|
<div class="menus">
|
||||||
<el-button type="primary" :disabled="taoyao !== null" @click="signalVisible = true">连接信令</el-button>
|
<el-button type="primary" :disabled="taoyao && taoyao.connect" @click="signalVisible = true">连接信令</el-button>
|
||||||
<el-button type="primary" @click="roomActive = 'enter';roomVisible = true;">选择房间</el-button>
|
<el-button type="primary" :disabled="!taoyao" @click="roomActive = 'enter';roomVisible = true;">选择房间</el-button>
|
||||||
<el-button type="primary" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button>
|
<el-button type="primary" :disabled="!taoyao" @click="roomActive = 'create';roomVisible = true;">创建房间</el-button>
|
||||||
<el-button>邀请终端</el-button>
|
<el-button :disabled="!taoyao || !room.roomId">邀请终端</el-button>
|
||||||
<el-button>退出房间</el-button>
|
<el-button :disabled="!taoyao || !room.roomId">退出房间</el-button>
|
||||||
<el-button @click="closeRoom()" type="danger">关闭房间</el-button>
|
<el-button :disabled="!taoyao || !room.roomId" @click="roomClose()" type="danger">关闭房间</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 终端 -->
|
<!-- 终端 -->
|
||||||
<div class="clients">
|
<div class="clients">
|
||||||
<LocalClient ref="local"></LocalClient>
|
<LocalClient ref="local" :client="taoyao" :taoyao="taoyao"></LocalClient>
|
||||||
<RemoteClient :ref="'remote-' + kv[0]" v-for="(kv, index) in remoteClients" :key="index"></RemoteClient>
|
<RemoteClient :ref="'remote-' + kv[0]" v-for="(kv, index) in remoteClients" :key="index" :client="kv[1]" :taoyao="taoyao"></RemoteClient>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -148,46 +150,48 @@ export default {
|
|||||||
this.rooms = await this.taoyao.roomList();
|
this.rooms = await this.taoyao.roomList();
|
||||||
this.medias = await this.taoyao.mediaList();
|
this.medias = await this.taoyao.mediaList();
|
||||||
},
|
},
|
||||||
async enterRoom() {
|
async roomClose() {
|
||||||
await this.taoyao.enterRoom(this.room.roomId, this.room.password);
|
this.taoyao.roomClose();
|
||||||
await this.taoyao.produceMedia();
|
|
||||||
this.roomVisible = false;
|
|
||||||
},
|
},
|
||||||
async roomCreate() {
|
async roomCreate() {
|
||||||
const room = await this.taoyao.roomCreate(this.room);
|
const room = await this.taoyao.roomCreate(this.room);
|
||||||
this.room.roomId = room.roomId;
|
this.room.roomId = room.roomId;
|
||||||
await this.enterRoom();
|
await this.roomEnter();
|
||||||
},
|
},
|
||||||
async closeRoom() {
|
async roomEnter() {
|
||||||
this.taoyao.closeRoom();
|
await this.taoyao.roomEnter(this.room.roomId, this.room.password);
|
||||||
|
await this.taoyao.produceMedia();
|
||||||
|
this.roomVisible = false;
|
||||||
|
},
|
||||||
|
audioVolume(message) {
|
||||||
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 信令回调
|
* 信令回调
|
||||||
*
|
*
|
||||||
* @param {*} data 消息
|
* @param {*} message 消息
|
||||||
* @param {*} error 异常
|
* @param {*} error 异常
|
||||||
*
|
*
|
||||||
* @return 是否继续执行
|
* @return 是否继续执行
|
||||||
*/
|
*/
|
||||||
async callback(data, error) {
|
async callback(message, error) {
|
||||||
let self = this;
|
const me = this;
|
||||||
switch (data.header.signal) {
|
switch (message.header.signal) {
|
||||||
case "client::config":
|
case "client::config":
|
||||||
self.roomVisible = true;
|
me.roomVisible = true;
|
||||||
|
break;
|
||||||
|
case "media::audio::active::speaker":
|
||||||
|
me.audioVolume(message);
|
||||||
break;
|
break;
|
||||||
case "client::register":
|
|
||||||
self.signalVisible = data.code !== "0000";
|
|
||||||
return true;
|
|
||||||
case "platform::error":
|
case "platform::error":
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("发生异常:", data, error);
|
console.error("发生异常:", message, error);
|
||||||
} else {
|
} else {
|
||||||
console.warn("发生错误:", data);
|
console.warn("发生错误:", message);
|
||||||
}
|
}
|
||||||
ElMessage({
|
ElMessage({
|
||||||
showClose: true,
|
|
||||||
message: data.message,
|
|
||||||
type: "error",
|
type: "error",
|
||||||
|
message: message.message,
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -195,14 +199,20 @@ export default {
|
|||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 媒体回调
|
* 媒体回调
|
||||||
|
*
|
||||||
|
* @param {*} type 类型
|
||||||
|
* @param {*} track 媒体Track
|
||||||
|
* @param {*} consumer 消费者
|
||||||
*/
|
*/
|
||||||
callbackMedia(type, track, consumer) {
|
callbackMedia(type, track, consumer) {
|
||||||
const self = this;
|
const me = this;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if(type === 'local') {
|
if(type === 'local') {
|
||||||
self.$refs.local.media(track);
|
me.$refs.local.media(track);
|
||||||
|
} else if(type === 'remote') {
|
||||||
|
me.$refs['remote-' + consumer.sourceId][0].media(track, consumer);
|
||||||
} else {
|
} else {
|
||||||
this.$refs['remote-' + consumer.sourceId][0].media(track, consumer);
|
// 其他
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -219,7 +229,9 @@ export default {
|
|||||||
.menus{width:100%;top:1rem;left:0;text-align:center;position:fixed;z-index:1;}
|
.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;}
|
.clients{width:100%;height:100%;top:0;left:0;position:fixed;}
|
||||||
.client{float:left;width:50vw;height:50vh;box-shadow:0 0 1px 0px rgba(0,0,0,0.4);}
|
.client{float:left;width:50vw;height:50vh;box-shadow:0 0 1px 0px rgba(0,0,0,0.4);}
|
||||||
.client .buttons{width:100%;bottom:1rem;left:0;text-align:center;position:absolute;padding:0.8rem 0;background:rgba(0,0,0,0.4);}
|
.client .buttons{width:100%;bottom:2px;left:0;text-align:center;position:absolute;padding:0.8rem 0;background:rgba(0,0,0,0.4);}
|
||||||
|
.client .buttons:after{width:0;height:2px;bottom:0;left:0;position:absolute;background:#C00;content:"";transition: all 400ms linear;}
|
||||||
.client audio{display:none;}
|
.client audio{display:none;}
|
||||||
.client video{width:100%;height:100%;}
|
.client video{width:100%;height:100%;}
|
||||||
|
.client .title{position:absolute;top:0;left:0;text-align:center;width:100%;}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<div class="client">
|
<div class="client">
|
||||||
<audio ref="audio"></audio>
|
<audio ref="audio"></audio>
|
||||||
<video ref="video"></video>
|
<video ref="video"></video>
|
||||||
<div class="buttons">
|
<p class="title">{{ client?.name || "" }}</p>
|
||||||
|
<div class="buttons" :style="{'--volume': client?.volume}">
|
||||||
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
|
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
|
||||||
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
|
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
|
||||||
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
|
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
|
||||||
@@ -54,7 +55,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
taoyao: null,
|
|
||||||
audio: null,
|
audio: null,
|
||||||
video: null,
|
video: null,
|
||||||
audioStream: null,
|
audioStream: null,
|
||||||
@@ -94,6 +94,14 @@ export default {
|
|||||||
this.audio = this.$refs.audio;
|
this.audio = this.$refs.audio;
|
||||||
this.video = this.$refs.video;
|
this.video = this.$refs.video;
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
"client": {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
"taoyao": {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
media(track) {
|
media(track) {
|
||||||
if (track.kind === "video") {
|
if (track.kind === "video") {
|
||||||
@@ -112,3 +120,6 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.client .buttons:after{width:var(--volume);}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
<div class="client">
|
<div class="client">
|
||||||
<audio ref="audio"></audio>
|
<audio ref="audio"></audio>
|
||||||
<video ref="video"></video>
|
<video ref="video"></video>
|
||||||
<div class="buttons">
|
<p class="title">{{ client?.name || "" }}</p>
|
||||||
|
<div class="buttons" :style="{'--volume': client?.volume}">
|
||||||
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
|
<el-button type="danger" title="打开麦克风" :icon="Mute" circle />
|
||||||
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
|
<el-button type="primary" title="关闭麦克风" :icon="Microphone" circle />
|
||||||
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
|
<el-button type="danger" title="打开摄像头" :icon="VideoPause" circle />
|
||||||
@@ -45,7 +46,6 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
taoyao: null,
|
|
||||||
audio: null,
|
audio: null,
|
||||||
video: null,
|
video: null,
|
||||||
audioStream: null,
|
audioStream: null,
|
||||||
@@ -59,6 +59,14 @@ export default {
|
|||||||
this.audio = this.$refs.audio;
|
this.audio = this.$refs.audio;
|
||||||
this.video = this.$refs.video;
|
this.video = this.$refs.video;
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
"client": {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
"taoyao": {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
media(track, consumer) {
|
media(track, consumer) {
|
||||||
if(track.kind === 'audio') {
|
if(track.kind === 'audio') {
|
||||||
@@ -86,3 +94,6 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.client .buttons:after{width:var(--volume);}
|
||||||
|
</style>
|
||||||
@@ -219,21 +219,38 @@ const signalChannel = {
|
|||||||
clearTimeout(me.reconnectTimer);
|
clearTimeout(me.reconnectTimer);
|
||||||
me.reconnection = false;
|
me.reconnection = false;
|
||||||
me.channel.close();
|
me.channel.close();
|
||||||
|
me.taoyao.connect = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 桃夭
|
* 远程终端
|
||||||
*/
|
*/
|
||||||
class Taoyao {
|
class RemoteClient {
|
||||||
// 信令连接
|
|
||||||
connect = false;
|
|
||||||
// 房间标识
|
|
||||||
roomId;
|
|
||||||
// 终端标识
|
|
||||||
clientId;
|
|
||||||
// 终端名称
|
// 终端名称
|
||||||
name;
|
name;
|
||||||
|
// 终端标识
|
||||||
|
clientId;
|
||||||
|
// 音量
|
||||||
|
volume = 0;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
name,
|
||||||
|
clientId,
|
||||||
|
}) {
|
||||||
|
this.name = name;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 桃夭
|
||||||
|
*/
|
||||||
|
class Taoyao extends RemoteClient {
|
||||||
|
// 信令连接
|
||||||
|
connect = false;
|
||||||
// 信令地址
|
// 信令地址
|
||||||
host;
|
host;
|
||||||
// 信令端口
|
// 信令端口
|
||||||
@@ -242,6 +259,8 @@ class Taoyao {
|
|||||||
username;
|
username;
|
||||||
// 信令密码
|
// 信令密码
|
||||||
password;
|
password;
|
||||||
|
// 房间标识
|
||||||
|
roomId;
|
||||||
// 回调事件
|
// 回调事件
|
||||||
callback;
|
callback;
|
||||||
// 媒体回调
|
// 媒体回调
|
||||||
@@ -249,13 +268,13 @@ class Taoyao {
|
|||||||
// 请求回调
|
// 请求回调
|
||||||
callbackMapping = new Map();
|
callbackMapping = new Map();
|
||||||
// 音频媒体配置
|
// 音频媒体配置
|
||||||
audio = defaultAudioConfig;
|
audioConfig = defaultAudioConfig;
|
||||||
// 视频媒体配置
|
// 视频媒体配置
|
||||||
video = defaultVideoConfig;
|
videoConfig = defaultVideoConfig;
|
||||||
// 媒体配置
|
// 媒体配置
|
||||||
media;
|
mediaConfig;
|
||||||
// WebRTC配置
|
// WebRTC配置
|
||||||
webrtc;
|
webrtcConfig;
|
||||||
// 信令通道
|
// 信令通道
|
||||||
signalChannel;
|
signalChannel;
|
||||||
// 发送媒体通道
|
// 发送媒体通道
|
||||||
@@ -296,13 +315,13 @@ class Taoyao {
|
|||||||
remoteClients = new Map();
|
remoteClients = new Map();
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
roomId,
|
|
||||||
clientId,
|
|
||||||
name,
|
name,
|
||||||
|
clientId,
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
roomId,
|
||||||
consume = true,
|
consume = true,
|
||||||
produce = true,
|
produce = true,
|
||||||
audioProduce = true,
|
audioProduce = true,
|
||||||
@@ -310,13 +329,14 @@ class Taoyao {
|
|||||||
forceTcp = false,
|
forceTcp = false,
|
||||||
dataProduce = true,
|
dataProduce = true,
|
||||||
}) {
|
}) {
|
||||||
this.roomId = roomId;
|
super({ name, clientId });
|
||||||
this.clientId = clientId;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.clientId = clientId;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.roomId = roomId;
|
||||||
this.consume = consume;
|
this.consume = consume;
|
||||||
this.produce = produce;
|
this.produce = produce;
|
||||||
this.dataProduce = produce && dataProduce;
|
this.dataProduce = produce && dataProduce;
|
||||||
@@ -401,6 +421,8 @@ class Taoyao {
|
|||||||
* 2. 执行前置回调
|
* 2. 执行前置回调
|
||||||
* 3. 如果注册全局回调,同时执行结果返回true不再执行后面所有回调。
|
* 3. 如果注册全局回调,同时执行结果返回true不再执行后面所有回调。
|
||||||
* 4. 执行后置回调
|
* 4. 执行后置回调
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
*/
|
*/
|
||||||
async on(message) {
|
async on(message) {
|
||||||
const me = this;
|
const me = this;
|
||||||
@@ -429,19 +451,22 @@ class Taoyao {
|
|||||||
/**
|
/**
|
||||||
* 前置回调
|
* 前置回调
|
||||||
*
|
*
|
||||||
* @param {*} message
|
* @param {*} message 消息
|
||||||
*/
|
*/
|
||||||
async preCallback(message) {
|
async preCallback(message) {
|
||||||
const self = this;
|
const me = this;
|
||||||
switch (message.header.signal) {
|
switch (message.header.signal) {
|
||||||
case "client::config":
|
case "client::config":
|
||||||
self.defaultClientConfig(message);
|
me.defaultClientConfig(message);
|
||||||
break;
|
break;
|
||||||
case "client::register":
|
case "client::register":
|
||||||
protocol.clientIndex = message.body.index;
|
me.defaultClientRegister(message);
|
||||||
break;
|
break;
|
||||||
case "media::consume":
|
case "media::consume":
|
||||||
await self.consumeMedia(message);
|
await me.defaultMediaConsume(message);
|
||||||
|
break;
|
||||||
|
case "platform::error":
|
||||||
|
me.defaultPlatformError(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,15 +478,18 @@ class Taoyao {
|
|||||||
async postCallback(message) {
|
async postCallback(message) {
|
||||||
const me = this;
|
const me = this;
|
||||||
switch (message.header.signal) {
|
switch (message.header.signal) {
|
||||||
case "room::client::list":
|
|
||||||
me.defaultRoomClientList(message);
|
|
||||||
break;
|
|
||||||
case "client::reboot":
|
case "client::reboot":
|
||||||
me.defaultClientReboot(message);
|
me.defaultClientReboot(message);
|
||||||
break;
|
break;
|
||||||
case "client::shutdown":
|
case "client::shutdown":
|
||||||
me.defaultClientShutdown(message);
|
me.defaultClientShutdown(message);
|
||||||
break;
|
break;
|
||||||
|
case "media::audio::active::speaker":
|
||||||
|
me.defaultMediaAudioActiveSpeaker(message);
|
||||||
|
break;
|
||||||
|
case "room::client::list":
|
||||||
|
me.defaultRoomClientList(message);
|
||||||
|
break;
|
||||||
case "room::close":
|
case "room::close":
|
||||||
me.defaultRoomClose(message);
|
me.defaultRoomClose(message);
|
||||||
break;
|
break;
|
||||||
@@ -471,14 +499,11 @@ class Taoyao {
|
|||||||
case "platform::error":
|
case "platform::error":
|
||||||
me.callbackError(message);
|
me.callbackError(message);
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
console.warn("不支持的信令:", message);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/************************ 信令 ************************/
|
/************************ 信令 ************************/
|
||||||
/**
|
/**
|
||||||
* 配置默认回调
|
* 终端配置信令
|
||||||
*
|
*
|
||||||
* @param {*} message 消息
|
* @param {*} message 消息
|
||||||
*/
|
*/
|
||||||
@@ -486,17 +511,17 @@ class Taoyao {
|
|||||||
const me = this;
|
const me = this;
|
||||||
const { media, webrtc } = message.body;
|
const { media, webrtc } = message.body;
|
||||||
const { audio, video } = media;
|
const { audio, video } = media;
|
||||||
me.audio.sampleSize = { min: media.minSampleSize, ideal: audio.sampleSize, max: media.maxSampleSize };
|
me.audioConfig.sampleSize = { min: media.minSampleSize, ideal: audio.sampleSize, max: media.maxSampleSize };
|
||||||
me.audio.sampleRate = { min: media.minSampleRate, ideal: audio.sampleRate, max: media.maxSampleRate };
|
me.audioConfig.sampleRate = { min: media.minSampleRate, ideal: audio.sampleRate, max: media.maxSampleRate };
|
||||||
me.video.width = { min: media.minWidth, ideal: video.width, max: media.maxWidth };
|
me.videoConfig.width = { min: media.minWidth, ideal: video.width, max: media.maxWidth };
|
||||||
me.video.height = { min: media.minHeight, ideal: video.height, max: media.maxHeight };
|
me.videoConfig.height = { min: media.minHeight, ideal: video.height, max: media.maxHeight };
|
||||||
me.video.frameRate = { min: media.minFrameRate, ideal: video.frameRate, max: media.maxFrameRate };
|
me.videoConfig.frameRate = { min: media.minFrameRate, ideal: video.frameRate, max: media.maxFrameRate };
|
||||||
me.media = media;
|
me.mediaConfig = media;
|
||||||
me.webrtc = webrtc;
|
me.webrtcConfig = webrtc;
|
||||||
console.debug("终端配置:", me.audio, me.video, me.media, me.webrtc);
|
console.debug("终端配置:", me.audioConfig, me.videoConfig, me.mediaConfig, me.webrtcConfig);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 终端重启默认回调
|
* 重启终端信令
|
||||||
*
|
*
|
||||||
* @param {*} message 消息
|
* @param {*} message 消息
|
||||||
*/
|
*/
|
||||||
@@ -505,7 +530,16 @@ class Taoyao {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 终端重启默认回调
|
* 终端注册信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
*/
|
||||||
|
defaultClientRegister(message) {
|
||||||
|
const { index } = message.body;
|
||||||
|
protocol.clientIndex = index;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 关闭终端信令
|
||||||
*
|
*
|
||||||
* @param {*} message 消息
|
* @param {*} message 消息
|
||||||
*/
|
*/
|
||||||
@@ -513,6 +547,118 @@ class Taoyao {
|
|||||||
console.info("关闭终端");
|
console.info("关闭终端");
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 当前讲话终端信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
*/
|
||||||
|
defaultMediaAudioActiveSpeaker(message) {
|
||||||
|
const me = this;
|
||||||
|
const { volume, clientId } = message.body;
|
||||||
|
if(!clientId) {
|
||||||
|
me.volume = 0;
|
||||||
|
me.remoteClients.forEach(v => v.volume = 0);
|
||||||
|
} if(me.clientId === clientId) {
|
||||||
|
me.volume = ((volume + 127) / 127 * 100) + "%";
|
||||||
|
} else {
|
||||||
|
const remoteClient = me.remoteClients.get(clientId);
|
||||||
|
if(remoteClient) {
|
||||||
|
remoteClient.volume = ((volume + 127) / 127 * 100) + "%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 消费媒体信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
*/
|
||||||
|
async defaultMediaConsume(message) {
|
||||||
|
const self = this;
|
||||||
|
if (!self.consume) {
|
||||||
|
console.log("没有消费媒体");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
kind,
|
||||||
|
type,
|
||||||
|
roomId,
|
||||||
|
clientId,
|
||||||
|
sourceId,
|
||||||
|
streamId,
|
||||||
|
producerId,
|
||||||
|
consumerId,
|
||||||
|
rtpParameters,
|
||||||
|
appData,
|
||||||
|
producerPaused,
|
||||||
|
} = message.body;
|
||||||
|
try {
|
||||||
|
const consumer = await self.recvTransport.consume({
|
||||||
|
id: consumerId,
|
||||||
|
kind,
|
||||||
|
producerId,
|
||||||
|
rtpParameters,
|
||||||
|
// NOTE: Force streamId to be same in mic and webcam and different
|
||||||
|
// in screen sharing so libwebrtc will just try to sync mic and
|
||||||
|
// webcam streams from the same remote peer.
|
||||||
|
//streamId: `${peerId}-${appData.share ? "share" : "mic-webcam"}`,
|
||||||
|
streamId: `${clientId}-${appData.share ? "share" : "mic-webcam"}`,
|
||||||
|
appData, // Trick.
|
||||||
|
});
|
||||||
|
consumer.clientId = clientId;
|
||||||
|
consumer.sourceId = sourceId;
|
||||||
|
consumer.streamId = streamId;
|
||||||
|
self.consumers.set(consumer.id, consumer);
|
||||||
|
consumer.on("transportclose", () => {
|
||||||
|
self.consumers.delete(consumer.id);
|
||||||
|
});
|
||||||
|
const { spatialLayers, temporalLayers } =
|
||||||
|
mediasoupClient.parseScalabilityMode(
|
||||||
|
consumer.rtpParameters.encodings[0].scalabilityMode
|
||||||
|
);
|
||||||
|
// store.dispatch(
|
||||||
|
// stateActions.addConsumer(
|
||||||
|
// {
|
||||||
|
// id: consumer.id,
|
||||||
|
// type: type,
|
||||||
|
// locallyPaused: false,
|
||||||
|
// remotelyPaused: producerPaused,
|
||||||
|
// rtpParameters: consumer.rtpParameters,
|
||||||
|
// spatialLayers: spatialLayers,
|
||||||
|
// temporalLayers: temporalLayers,
|
||||||
|
// preferredSpatialLayer: spatialLayers - 1,
|
||||||
|
// preferredTemporalLayer: temporalLayers - 1,
|
||||||
|
// priority: 1,
|
||||||
|
// codec: consumer.rtpParameters.codecs[0].mimeType.split("/")[1],
|
||||||
|
// track: consumer.track,
|
||||||
|
// },
|
||||||
|
// peerId
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
self.push(message);
|
||||||
|
console.log("消费者", consumer);
|
||||||
|
|
||||||
|
self.callbackMedia("remote", consumer.track, consumer);
|
||||||
|
|
||||||
|
// If audio-only mode is enabled, pause it.
|
||||||
|
if (consumer.kind === "video" && !self.videoProduce) {
|
||||||
|
// this.pauseConsumer(consumer);
|
||||||
|
// TODO:实现
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
self.callbackError("消费媒体异常", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 平台异常信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
*/
|
||||||
|
defaultPlatformError(message) {
|
||||||
|
const { code } = message;
|
||||||
|
if(code === "3401") {
|
||||||
|
signalChannel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 房间终端列表信令
|
* 房间终端列表信令
|
||||||
*
|
*
|
||||||
@@ -524,10 +670,23 @@ class Taoyao {
|
|||||||
if (v.clientId === me.clientId) {
|
if (v.clientId === me.clientId) {
|
||||||
// 忽略自己
|
// 忽略自己
|
||||||
} else {
|
} else {
|
||||||
me.remoteClients.set(v.clientId, me.roomId);
|
me.remoteClients.set(v.clientId, new RemoteClient(v));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 关闭房间信令
|
||||||
|
*/
|
||||||
|
async roomClose() {
|
||||||
|
const me = this;
|
||||||
|
if(!me.roomId) {
|
||||||
|
console.warn("房间无效:", me.roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
me.push(protocol.buildMessage("room::close", {
|
||||||
|
roomId: me.roomId
|
||||||
|
}));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 关闭房间信令
|
* 关闭房间信令
|
||||||
*
|
*
|
||||||
@@ -560,12 +719,48 @@ class Taoyao {
|
|||||||
);
|
);
|
||||||
return response.body;
|
return response.body;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 进入房间信令
|
||||||
|
*
|
||||||
|
* @param {*} roomId 房间ID
|
||||||
|
* @param {*} password 房间密码
|
||||||
|
*/
|
||||||
|
async roomEnter(roomId, password) {
|
||||||
|
const me = this;
|
||||||
|
if (!roomId) {
|
||||||
|
this.callbackError("无效房间");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
me.roomId = roomId;
|
||||||
|
me.mediasoupDevice = new mediasoupClient.Device();
|
||||||
|
const response = await me.request(
|
||||||
|
protocol.buildMessage("media::router::rtp::capabilities", {
|
||||||
|
roomId: me.roomId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const routerRtpCapabilities = response.body.rtpCapabilities;
|
||||||
|
await me.mediasoupDevice.load({ routerRtpCapabilities });
|
||||||
|
await me.request(
|
||||||
|
protocol.buildMessage("room::enter", {
|
||||||
|
roomId: roomId,
|
||||||
|
password: password,
|
||||||
|
rtpCapabilities: me.consume ? me.mediasoupDevice.rtpCapabilities : undefined,
|
||||||
|
sctpCapabilities: me.consume && me.dataProduce ? me.mediasoupDevice.sctpCapabilities : undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 进入房间信令
|
||||||
|
*
|
||||||
|
* @param {*} message 消息
|
||||||
|
*/
|
||||||
defaultRoomEnter(message) {
|
defaultRoomEnter(message) {
|
||||||
const { roomId, clientId } = message.body;
|
const me = this;
|
||||||
if (clientId === this.clientId) {
|
const { roomId, clientId, status } = message.body;
|
||||||
|
if (clientId === me.clientId) {
|
||||||
// 忽略自己
|
// 忽略自己
|
||||||
} else {
|
} else {
|
||||||
this.remoteClients.set(clientId, roomId);
|
me.remoteClients.set(clientId, new RemoteClient(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -607,45 +802,6 @@ class Taoyao {
|
|||||||
);
|
);
|
||||||
return response.body;
|
return response.body;
|
||||||
}
|
}
|
||||||
async enterRoom(roomId, password) {
|
|
||||||
const self = this;
|
|
||||||
if (!roomId) {
|
|
||||||
this.callbackError("无效房间");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.roomId = roomId;
|
|
||||||
self.mediasoupDevice = new mediasoupClient.Device();
|
|
||||||
const response = await self.request(
|
|
||||||
protocol.buildMessage("media::router::rtp::capabilities", {
|
|
||||||
roomId: self.roomId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const routerRtpCapabilities = response.body.rtpCapabilities;
|
|
||||||
await self.mediasoupDevice.load({ routerRtpCapabilities });
|
|
||||||
await self.request(
|
|
||||||
protocol.buildMessage("room::enter", {
|
|
||||||
roomId: roomId,
|
|
||||||
password: password,
|
|
||||||
rtpCapabilities: self.consume
|
|
||||||
? self.mediasoupDevice.rtpCapabilities
|
|
||||||
: undefined,
|
|
||||||
sctpCapabilities:
|
|
||||||
self.consume && self.dataProduce
|
|
||||||
? self.mediasoupDevice.sctpCapabilities
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async closeRoom() {
|
|
||||||
const me = this;
|
|
||||||
if(!me.roomId) {
|
|
||||||
console.warn("房间无效:", me.roomId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
me.push(protocol.buildMessage("room::close", {
|
|
||||||
roomId: me.roomId
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
/************************ 媒体 ************************/
|
/************************ 媒体 ************************/
|
||||||
/**
|
/**
|
||||||
* 生产媒体
|
* 生产媒体
|
||||||
@@ -841,7 +997,7 @@ class Taoyao {
|
|||||||
try {
|
try {
|
||||||
console.debug("打开麦克风");
|
console.debug("打开麦克风");
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: self.audio,
|
audio: self.audioConfig,
|
||||||
});
|
});
|
||||||
const tracks = stream.getAudioTracks();
|
const tracks = stream.getAudioTracks();
|
||||||
if (tracks.length > 1) {
|
if (tracks.length > 1) {
|
||||||
@@ -1107,89 +1263,6 @@ class Taoyao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 消费媒体
|
|
||||||
*
|
|
||||||
* @param {*} message
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async consumeMedia(message) {
|
|
||||||
const self = this;
|
|
||||||
if (!self.consume) {
|
|
||||||
console.log("没有消费媒体");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
kind,
|
|
||||||
type,
|
|
||||||
roomId,
|
|
||||||
clientId,
|
|
||||||
sourceId,
|
|
||||||
streamId,
|
|
||||||
producerId,
|
|
||||||
consumerId,
|
|
||||||
rtpParameters,
|
|
||||||
appData,
|
|
||||||
producerPaused,
|
|
||||||
} = message.body;
|
|
||||||
try {
|
|
||||||
const consumer = await self.recvTransport.consume({
|
|
||||||
id: consumerId,
|
|
||||||
kind,
|
|
||||||
producerId,
|
|
||||||
rtpParameters,
|
|
||||||
// NOTE: Force streamId to be same in mic and webcam and different
|
|
||||||
// in screen sharing so libwebrtc will just try to sync mic and
|
|
||||||
// webcam streams from the same remote peer.
|
|
||||||
//streamId: `${peerId}-${appData.share ? "share" : "mic-webcam"}`,
|
|
||||||
streamId: `${clientId}-${appData.share ? "share" : "mic-webcam"}`,
|
|
||||||
appData, // Trick.
|
|
||||||
});
|
|
||||||
consumer.clientId = clientId;
|
|
||||||
consumer.sourceId = sourceId;
|
|
||||||
consumer.streamId = streamId;
|
|
||||||
self.consumers.set(consumer.id, consumer);
|
|
||||||
consumer.on("transportclose", () => {
|
|
||||||
self.consumers.delete(consumer.id);
|
|
||||||
});
|
|
||||||
const { spatialLayers, temporalLayers } =
|
|
||||||
mediasoupClient.parseScalabilityMode(
|
|
||||||
consumer.rtpParameters.encodings[0].scalabilityMode
|
|
||||||
);
|
|
||||||
// store.dispatch(
|
|
||||||
// stateActions.addConsumer(
|
|
||||||
// {
|
|
||||||
// id: consumer.id,
|
|
||||||
// type: type,
|
|
||||||
// locallyPaused: false,
|
|
||||||
// remotelyPaused: producerPaused,
|
|
||||||
// rtpParameters: consumer.rtpParameters,
|
|
||||||
// spatialLayers: spatialLayers,
|
|
||||||
// temporalLayers: temporalLayers,
|
|
||||||
// preferredSpatialLayer: spatialLayers - 1,
|
|
||||||
// preferredTemporalLayer: temporalLayers - 1,
|
|
||||||
// priority: 1,
|
|
||||||
// codec: consumer.rtpParameters.codecs[0].mimeType.split("/")[1],
|
|
||||||
// track: consumer.track,
|
|
||||||
// },
|
|
||||||
// peerId
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
self.push(message);
|
|
||||||
console.log("消费者", consumer);
|
|
||||||
|
|
||||||
self.callbackMedia("remote", consumer.track, consumer);
|
|
||||||
|
|
||||||
// If audio-only mode is enabled, pause it.
|
|
||||||
if (consumer.kind === "video" && !self.videoProduce) {
|
|
||||||
// this.pauseConsumer(consumer);
|
|
||||||
// TODO:实现
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
self.callbackError("消费媒体异常", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pauseConsumer(consumer) {
|
async pauseConsumer(consumer) {
|
||||||
if (consumer.paused) return;
|
if (consumer.paused) return;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -13,7 +13,3 @@
|
|||||||
|
|
||||||
[信令格式](https://localhost:8888/protocol/list)
|
[信令格式](https://localhost:8888/protocol/list)
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
标识 -> ID
|
|
||||||
所有字段获取 -> get
|
|
||||||
@@ -196,7 +196,7 @@ public interface Constant {
|
|||||||
*/
|
*/
|
||||||
String RTP_PARAMETERS = "rtpParameters";
|
String RTP_PARAMETERS = "rtpParameters";
|
||||||
/**
|
/**
|
||||||
* RTP能力
|
* RTP协商
|
||||||
*/
|
*/
|
||||||
String RTP_CAPABILITIES = "rtpCapabilities";
|
String RTP_CAPABILITIES = "rtpCapabilities";
|
||||||
/**
|
/**
|
||||||
@@ -208,7 +208,7 @@ public interface Constant {
|
|||||||
*/
|
*/
|
||||||
String SCTP_PARAMETERS = "sctpParameters";
|
String SCTP_PARAMETERS = "sctpParameters";
|
||||||
/**
|
/**
|
||||||
* SCTP能力
|
* SCTP协商
|
||||||
*/
|
*/
|
||||||
String SCTP_CAPABILITIES = "sctpCapabilities";
|
String SCTP_CAPABILITIES = "sctpCapabilities";
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.acgist.taoyao.signal.event;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
|
import com.acgist.taoyao.signal.party.media.Room;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间终端列表事件
|
||||||
|
*
|
||||||
|
* @author acgist
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class RoomClientListEvent extends RoomEventAdapter {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端
|
||||||
|
*/
|
||||||
|
private final Client client;
|
||||||
|
|
||||||
|
public RoomClientListEvent(Room room, Client client) {
|
||||||
|
super(room);
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -76,7 +76,13 @@ public class ClientWrapper implements AutoCloseable {
|
|||||||
* 没有订阅任何媒体时需要用户自己对媒体进行消费控制
|
* 没有订阅任何媒体时需要用户自己对媒体进行消费控制
|
||||||
*/
|
*/
|
||||||
private SubscribeType subscribeType;
|
private SubscribeType subscribeType;
|
||||||
|
/**
|
||||||
|
* RTP协商
|
||||||
|
*/
|
||||||
private Object rtpCapabilities;
|
private Object rtpCapabilities;
|
||||||
|
/**
|
||||||
|
* SCTP协商
|
||||||
|
*/
|
||||||
private Object sctpCapabilities;
|
private Object sctpCapabilities;
|
||||||
/**
|
/**
|
||||||
* 发送通道
|
* 发送通道
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public class Room implements Closeable {
|
|||||||
if(clientWrapper != null) {
|
if(clientWrapper != null) {
|
||||||
return clientWrapper;
|
return clientWrapper;
|
||||||
}
|
}
|
||||||
|
log.info("终端进入房间:{} - {}", this.roomId, client.clientId());
|
||||||
clientWrapper = new ClientWrapper(this, client);
|
clientWrapper = new ClientWrapper(this, client);
|
||||||
this.clients.put(client, clientWrapper);
|
this.clients.put(client, clientWrapper);
|
||||||
this.roomStatus.setClientSize(this.roomStatus.getClientSize() + 1);
|
this.roomStatus.setClientSize(this.roomStatus.getClientSize() + 1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.acgist.taoyao.signal.protocol.media;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.acgist.taoyao.boot.annotation.Description;
|
||||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
@@ -15,6 +16,15 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
|||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@Protocol
|
@Protocol
|
||||||
|
@Description(
|
||||||
|
body = """
|
||||||
|
{
|
||||||
|
"volume": 音量,
|
||||||
|
"clientId": "终端ID"
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
flow = "媒体服务->信令服务->终端"
|
||||||
|
)
|
||||||
public class MediaAudioActiveSpeakerProtocol extends ProtocolRoomAdapter {
|
public class MediaAudioActiveSpeakerProtocol extends ProtocolRoomAdapter {
|
||||||
|
|
||||||
public static final String SIGNAL = "media::audio::active::speaker";
|
public static final String SIGNAL = "media::audio::active::speaker";
|
||||||
@@ -28,7 +38,7 @@ public class MediaAudioActiveSpeakerProtocol extends ProtocolRoomAdapter {
|
|||||||
if(clientType == ClientType.MEDIA) {
|
if(clientType == ClientType.MEDIA) {
|
||||||
room.broadcast(message);
|
room.broadcast(message);
|
||||||
} else {
|
} else {
|
||||||
// 忽略其他情况
|
this.logNoAdapter(clientType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import com.acgist.taoyao.signal.party.media.Room;
|
|||||||
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由RTP能力信令
|
* 路由RTP协商信令
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +37,7 @@ public class MediaRouterRtpCapabilitiesProtocol extends ProtocolRoomAdapter {
|
|||||||
public static final String SIGNAL = "media::router::rtp::capabilities";
|
public static final String SIGNAL = "media::router::rtp::capabilities";
|
||||||
|
|
||||||
public MediaRouterRtpCapabilitiesProtocol() {
|
public MediaRouterRtpCapabilitiesProtocol() {
|
||||||
super("路由RTP能力信令", SIGNAL);
|
super("路由RTP协商信令", SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,7 +45,7 @@ public class MediaRouterRtpCapabilitiesProtocol extends ProtocolRoomAdapter {
|
|||||||
if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) {
|
if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) {
|
||||||
client.push(room.request(message));
|
client.push(room.request(message));
|
||||||
} else {
|
} else {
|
||||||
// 忽略其他情况
|
this.logNoAdapter(clientType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ package com.acgist.taoyao.signal.protocol.room;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.annotation.Description;
|
import com.acgist.taoyao.boot.annotation.Description;
|
||||||
import com.acgist.taoyao.boot.annotation.Protocol;
|
import com.acgist.taoyao.boot.annotation.Protocol;
|
||||||
import com.acgist.taoyao.boot.model.Message;
|
import com.acgist.taoyao.boot.model.Message;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
import com.acgist.taoyao.signal.client.ClientType;
|
import com.acgist.taoyao.signal.client.ClientType;
|
||||||
|
import com.acgist.taoyao.signal.event.RoomClientListEvent;
|
||||||
import com.acgist.taoyao.signal.party.media.Room;
|
import com.acgist.taoyao.signal.party.media.Room;
|
||||||
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
|||||||
},
|
},
|
||||||
flow = "终端=>信令服务->终端"
|
flow = "终端=>信令服务->终端"
|
||||||
)
|
)
|
||||||
public class RoomClientListProtocol extends ProtocolRoomAdapter {
|
public class RoomClientListProtocol extends ProtocolRoomAdapter implements ApplicationListener<RoomClientListEvent> {
|
||||||
|
|
||||||
public static final String SIGNAL = "room::client::list";
|
public static final String SIGNAL = "room::client::list";
|
||||||
|
|
||||||
@@ -57,6 +61,14 @@ public class RoomClientListProtocol extends ProtocolRoomAdapter {
|
|||||||
super("房间终端列表信令", SIGNAL);
|
super("房间终端列表信令", SIGNAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(RoomClientListEvent event) {
|
||||||
|
final Room room = event.getRoom();
|
||||||
|
final Client client = event.getClient();
|
||||||
|
client.push(this.build(room.clientStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
||||||
message.setBody(room.clientStatus());
|
message.setBody(room.clientStatus());
|
||||||
|
|||||||
@@ -13,25 +13,24 @@ import com.acgist.taoyao.boot.model.MessageCodeException;
|
|||||||
import com.acgist.taoyao.boot.utils.MapUtils;
|
import com.acgist.taoyao.boot.utils.MapUtils;
|
||||||
import com.acgist.taoyao.signal.client.Client;
|
import com.acgist.taoyao.signal.client.Client;
|
||||||
import com.acgist.taoyao.signal.client.ClientType;
|
import com.acgist.taoyao.signal.client.ClientType;
|
||||||
|
import com.acgist.taoyao.signal.event.RoomClientListEvent;
|
||||||
import com.acgist.taoyao.signal.party.media.ClientWrapper;
|
import com.acgist.taoyao.signal.party.media.ClientWrapper;
|
||||||
import com.acgist.taoyao.signal.party.media.ClientWrapper.SubscribeType;
|
import com.acgist.taoyao.signal.party.media.ClientWrapper.SubscribeType;
|
||||||
import com.acgist.taoyao.signal.party.media.Room;
|
import com.acgist.taoyao.signal.party.media.Room;
|
||||||
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
import com.acgist.taoyao.signal.protocol.ProtocolRoomAdapter;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进入房间信令
|
* 进入房间信令
|
||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Protocol
|
@Protocol
|
||||||
@Description(
|
@Description(
|
||||||
body = {
|
body = {
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
"roomId": "房间标识"
|
"roomId": "房间ID",
|
||||||
|
"password": "房间密码(选填)"
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
@@ -47,39 +46,52 @@ public class RoomEnterProtocol extends ProtocolRoomAdapter {
|
|||||||
|
|
||||||
public static final String SIGNAL = "room::enter";
|
public static final String SIGNAL = "room::enter";
|
||||||
|
|
||||||
private final RoomClientListProtocol roomClientListProtocol;
|
public RoomEnterProtocol() {
|
||||||
|
|
||||||
public RoomEnterProtocol(RoomClientListProtocol roomClientListProtocol) {
|
|
||||||
super("进入房间信令", SIGNAL);
|
super("进入房间信令", SIGNAL);
|
||||||
this.roomClientListProtocol = roomClientListProtocol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
public void execute(String clientId, ClientType clientType, Room room, Client client, Client mediaClient, Message message, Map<String, Object> body) {
|
||||||
|
if(clientType == ClientType.WEB || clientType == ClientType.CAMERA) {
|
||||||
|
this.enter(clientId, room, client, message, body);
|
||||||
|
} else {
|
||||||
|
this.logNoAdapter(clientType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端进入
|
||||||
|
*
|
||||||
|
* @param clientId 终端ID
|
||||||
|
* @param room 房间
|
||||||
|
* @param client 终端
|
||||||
|
* @param message 消息
|
||||||
|
* @param body 消息主体
|
||||||
|
*/
|
||||||
|
private void enter(String clientId, Room room, Client client, Message message, Map<String, Object> body) {
|
||||||
final String password = MapUtils.get(body, Constant.PASSWORD);
|
final String password = MapUtils.get(body, Constant.PASSWORD);
|
||||||
final String subscribeType = MapUtils.get(body, Constant.SUBSCRIBE_TYPE);
|
|
||||||
final Object rtpCapabilities = MapUtils.get(body, Constant.RTP_CAPABILITIES);
|
|
||||||
final Object sctpCapabilities = MapUtils.get(body, Constant.SCTP_CAPABILITIES);
|
|
||||||
final String roomPassowrd = room.getPassword();
|
final String roomPassowrd = room.getPassword();
|
||||||
if(StringUtils.isNotEmpty(roomPassowrd) && !roomPassowrd.equals(password)) {
|
if(StringUtils.isNotEmpty(roomPassowrd) && !roomPassowrd.equals(password)) {
|
||||||
throw MessageCodeException.of(MessageCode.CODE_3401, "密码错误");
|
throw MessageCodeException.of(MessageCode.CODE_3401, "密码错误");
|
||||||
}
|
}
|
||||||
|
final String subscribeType = MapUtils.get(body, Constant.SUBSCRIBE_TYPE);
|
||||||
|
final Object rtpCapabilities = MapUtils.get(body, Constant.RTP_CAPABILITIES);
|
||||||
|
final Object sctpCapabilities = MapUtils.get(body, Constant.SCTP_CAPABILITIES);
|
||||||
// 进入房间
|
// 进入房间
|
||||||
final ClientWrapper clientWrapper = room.enter(client);
|
final ClientWrapper clientWrapper = room.enter(client);
|
||||||
|
// 配置参数
|
||||||
clientWrapper.setSubscribeType(SubscribeType.of(subscribeType));
|
clientWrapper.setSubscribeType(SubscribeType.of(subscribeType));
|
||||||
clientWrapper.setRtpCapabilities(rtpCapabilities);
|
clientWrapper.setRtpCapabilities(rtpCapabilities);
|
||||||
clientWrapper.setSctpCapabilities(sctpCapabilities);
|
clientWrapper.setSctpCapabilities(sctpCapabilities);
|
||||||
// 发送通知
|
// 发送通知
|
||||||
message.setBody(Map.of(
|
message.setBody(Map.of(
|
||||||
Constant.ROOM_ID, room.getRoomId(),
|
Constant.ROOM_ID, room.getRoomId(),
|
||||||
Constant.CLIENT_ID, clientId
|
Constant.CLIENT_ID, clientId,
|
||||||
|
Constant.STATUS, client.status()
|
||||||
));
|
));
|
||||||
room.broadcast(message);
|
room.broadcast(message);
|
||||||
log.info("进入房间:{} - {}", clientId, room.getRoomId());
|
// 房间终端列表事件
|
||||||
// 推送房间用户信息:TODO event
|
this.publishEvent(new RoomClientListEvent(room, client));
|
||||||
final Message roomClientListMessage = this.roomClientListProtocol.build();
|
|
||||||
roomClientListMessage.setBody(room.clientStatus());
|
|
||||||
client.push(roomClientListMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user