[+] 整体架构调整

生产者接入完成
媒体作为信令服务的终端注册
This commit is contained in:
acgist
2023-02-25 13:31:57 +08:00
parent 6358255458
commit 129c36ed80
156 changed files with 3659 additions and 2817 deletions

View File

@@ -1,6 +1,12 @@
<!-- 桃夭 -->
<template>
<SettingRoom :taoyao="taoyao" :roomVisible="roomVisible" @buildMedia="buildMedia"></SettingRoom>
<div class="menu">
<el-button type="primary" @click="signalVisible = true">连接信令</el-button>
<el-button type="primary" @click="roomVisible = true">选择房间</el-button>
<el-button type="danger">关闭房间</el-button>
<el-button>退出房间</el-button>
</div>
<SettingRoom :taoyao="taoyao" :roomVisible="roomVisible" @produceMedia="produceMedia"></SettingRoom>
<SettingSignal :signalVisible="signalVisible" @buildSignal="buildSignal"></SettingSignal>
</template>
@@ -15,11 +21,14 @@ export default {
return {
taoyao: {},
roomVisible: false,
signalVisible: true,
signalVisible: false,
};
},
mounted() {
console.info("桃夭终端开始启动");
console.info(`
中庭地白树栖鸦,冷露无声湿桂花。
今夜月明人尽望,不知秋思落谁家。
`);
},
methods: {
buildSignal(config) {
@@ -28,19 +37,24 @@ export default {
self.signalVisible = false;
self.taoyao.buildSignal(self.callback);
},
buildMedia(roomId) {
produceMedia() {
let self = this;
self.taoyao.buildMedia(roomId);
self.taoyao.produceMedia();
},
/**
* 信令回调
*
* @param {*} data 消息
* @param {*} error 异常
*
* @return 是否继续执行
*/
callback(data) {
callback(data, error) {
let self = this;
if(data.header.signal === "platform::error") {
console.error("发生异常:", data, error);
return false;
}
switch (data.header.signal) {
case "client::config":
self.roomVisible = true;

View File

@@ -3,20 +3,17 @@
*/
const protocol = {
// 当前索引
index: 100000,
// 最小索引
minIndex: 100000,
index: 0,
// 最大索引
maxIndex: 999999,
maxIndex: 1000,
/**
* @returns 索引
*/
buildId() {
const self = this;
if (self.index++ >= self.maxIndex) {
self.index = self.minIndex;
if (++this.index >= this.maxIndex) {
this.index = 0;
}
return Date.now() + "" + self.index;
return Date.now() * 1000 + this.index;
},
/**
* @param {*} signal 信令标识

View File

@@ -1,2 +1,14 @@
<!-- 远程终端 -->
<template></template>
<template>
<video></video>
<audio></audio>
<div>
<el-button type="primary" title="关闭麦克风" :icon="Edit" circle />
</div>
</template>
<script>
export default {
name: "RemoteClient",
}
</script>

View File

@@ -16,7 +16,7 @@
<el-option
v-for="value in rooms"
:key="value.roomId"
:label="value.name"
:label="value.name || value.roomId"
:value="value.roomId"
/>
</el-select>
@@ -26,10 +26,10 @@
<el-form-item label="媒体服务">
<el-select v-model="room.mediaId" placeholder="媒体服务标识">
<el-option
v-for="value in taoyao.mediaServerList"
:key="value.mediaId"
:label="value.mediaId"
:value="value.mediaId"
v-for="value in medias"
:key="value.clientId"
:label="value.name || value.clientId"
:value="value.clientId"
/>
</el-select>
</el-form-item>
@@ -62,6 +62,7 @@ export default {
return {
room: {},
rooms: [],
medias: [],
activeName: "enter",
localVisible: false,
};
@@ -77,28 +78,26 @@ export default {
},
methods: {
async init() {
const response = await this.taoyao.request(
const roomResponse = await this.taoyao.request(
protocol.buildMessage("room::list")
);
this.rooms = response.body;
this.rooms = roomResponse.body;
const mediaResponse = await this.taoyao.request(
protocol.buildMessage("client::list", {clientType:"MEDIA"})
);
this.medias = mediaResponse.body;
},
async enter() {
await this.taoyao.request(
protocol.buildMessage("room::enter", {
...this.room,
})
);
await this.taoyao.enter(this.room.roomId);
this.localVisible = false;
this.$emit("buildMedia", this.room.roomId);
this.$emit("produceMedia");
},
async create() {
const response = await this.taoyao.request(
protocol.buildMessage("room::create", {
...this.room,
})
);
const room = await this.taoyao.create(this.room);
this.room.roomId = room.roomId;
await this.enter(room.roomId);
this.localVisible = false;
this.$emit("buildMedia", response.body.roomId);
this.$emit("produceMedia");
},
},
};

View File

@@ -42,11 +42,11 @@ export default {
username: "taoyao",
password: "taoyao",
},
localVisible: true,
localVisible: false,
};
},
props: {
signalVisible: true,
signalVisible: false,
},
watch: {
signalVisible() {

View File

@@ -51,6 +51,7 @@ const signalChannel = {
self.heartbeatTimer = setTimeout(async function () {
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
const battery = await navigator.getBattery();
// TODO信号强度
self.push(
protocol.buildMessage("client::heartbeat", {
signal: 100,
@@ -60,7 +61,7 @@ const signalChannel = {
);
self.heartbeat();
} else {
console.warn("发送心跳失败", self.channel);
console.warn("发送心跳失败", self.address);
}
}, self.heartbeatTime);
},
@@ -76,22 +77,26 @@ const signalChannel = {
async connect(address, callback, reconnection = true) {
const self = this;
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
return;
return new Promise((resolve, reject) => {
resolve(self.channel);
});
}
self.address = address;
self.callback = callback;
self.reconnection = reconnection;
return new Promise((resolve, reject) => {
console.debug("连接信令通道", address);
self.channel = new WebSocket(address);
self.channel.onopen = async function (e) {
console.debug("打开信令通道", e);
console.debug("连接信令通道", self.address);
self.channel = new WebSocket(self.address);
self.channel.onopen = async function () {
console.debug("打开信令通道", self.address);
// 注册终端
// TODO信号强度
const battery = await navigator.getBattery();
self.push(
protocol.buildMessage("client::register", {
ip: "localhost",
name: "桃夭Web",
clientId: self.taoyao.clientId,
clientType: "WEB",
signal: 100,
battery: battery.level * 100,
charging: battery.charging,
@@ -104,21 +109,21 @@ const signalChannel = {
// 开始心跳
self.heartbeat();
// 成功回调
resolve(e);
resolve(self.channel);
};
self.channel.onclose = function (e) {
console.error("信令通道关闭", self.channel, e);
self.channel.onclose = async function () {
console.warn("信令通道关闭", self.channel);
if (self.reconnection) {
self.reconnect();
}
reject(e);
// 不要失败回调
};
self.channel.onerror = function (e) {
console.error("信令通道异常", self.channel, e);
self.channel.onerror = async function (e) {
console.error("信令通道异常", self.channel, e);
if (self.reconnection) {
self.reconnect();
}
reject(e);
// 不要失败回调
};
/**
* 回调策略:
@@ -127,7 +132,7 @@ const signalChannel = {
* 3. 如果前面所有回调没有返回true执行默认回调。
*/
self.channel.onmessage = function (e) {
console.debug("信令通道消息", e.data);
console.debug("信令通道消息", e.data);
let done = false;
const message = JSON.parse(e.data);
// 请求回调
@@ -154,22 +159,20 @@ const signalChannel = {
*/
reconnect() {
const self = this;
if (self.lockReconnect) {
if (
self.lockReconnect ||
(self.channel && self.channel.readyState === WebSocket.OPEN)
) {
return;
}
self.lockReconnect = true;
// 关闭旧的通道
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
self.channel.close();
self.channel = null;
}
if (self.reconnectTimer) {
clearTimeout(self.reconnectTimer);
}
// 打开定时重连
// 定时重连
self.reconnectTimer = setTimeout(function () {
console.info("信令通道重连", self.address);
self.connect(self.address, self.callback, true);
console.info("信令通道重连", self.address);
self.connect(self.address, self.callback, self.reconnection);
self.lockReconnect = false;
}, self.connectionTimeout);
self.connectionTimeout = Math.min(
@@ -190,7 +193,11 @@ const signalChannel = {
self.callbackMapping.set(message.header.id, callback);
}
// 发送消息
self.channel.send(JSON.stringify(message));
try {
self.channel.send(JSON.stringify(message));
} catch (error) {
console.error("推送消息异常:", message, error);
}
},
/**
* 同步请求
@@ -203,17 +210,23 @@ const signalChannel = {
const self = this;
return new Promise((resolve, reject) => {
let done = false;
// 设置回调
// 注册回调
self.callbackMapping.set(message.header.id, (response) => {
resolve(response);
done = true;
// 返回true不要继续执行回调
return true;
});
// 发送请求
self.channel.send(JSON.stringify(message));
// 发送消息
try {
self.channel.send(JSON.stringify(message));
} catch (error) {
console.error("请求消息异常:", message, error);
}
// 设置超时
setTimeout(() => {
if (!done) {
self.callbackMapping.delete(message.header.id);
reject("请求超时", message);
}
}, 5000);
@@ -223,7 +236,7 @@ const signalChannel = {
* 关闭通道
*/
close() {
let self = this;
const self = this;
self.reconnection = false;
self.channel.close();
clearTimeout(self.heartbeatTimer);
@@ -244,11 +257,14 @@ const signalChannel = {
case "client::reboot":
self.defaultClientReboot(message);
break;
case "client::shutdown":
self.defaultClientShutdown(message);
break;
case "client::register":
console.info("终端注册成功");
break;
case "platform::error":
console.error("信令异常", message);
self.callbackError(message);
break;
}
},
@@ -262,13 +278,11 @@ const signalChannel = {
self.taoyao.audio = { ...defaultAudioConfig, ...message.body.media.audio };
self.taoyao.video = { ...defaultVideoConfig, ...message.body.media.video };
self.taoyao.webrtc = message.body.webrtc;
self.taoyao.mediaServerList = message.body.media.mediaServerList;
console.debug(
"终端配置",
self.taoyao.audio,
self.taoyao.video,
self.taoyao.webrtc,
self.taoyao.mediaServerList
self.taoyao.webrtc
);
},
/**
@@ -280,6 +294,15 @@ const signalChannel = {
console.info("重启终端");
location.reload();
},
/**
* 终端重启默认回调
*
* @param {*} message 消息
*/
defaultClientShutdown(message) {
console.info("关闭终端");
window.close();
},
};
/**
@@ -298,14 +321,14 @@ class Taoyao {
username;
// 信令密码
password;
// 回调事件
callback;
// 音频媒体配置
audio;
// 视频媒体配置
video;
// WebRTC配置
webrtc;
// 媒体服务配置
mediaServerList;
// 发送信令
push;
// 请求信令
@@ -382,8 +405,9 @@ class Taoyao {
*/
async buildSignal(callback) {
const self = this;
signalChannel.taoyao = self;
self.callback = callback;
self.signalChannel = signalChannel;
signalChannel.taoyao = self;
// 不能直接this.push = this.signalChannel.push这样导致this对象错误
self.push = function (data, pushCallback) {
self.signalChannel.push(data, pushCallback);
@@ -397,27 +421,59 @@ class Taoyao {
);
}
/**
* 打开媒体通道
* TODO共享 navigator.mediaDevices.getDisplayMedia();
* 错误回调
*/
async buildMedia(roomId) {
let self = this;
// 释放资源
self.closeMedia();
if (roomId) {
self.roomId = roomId;
callbackError(message, error) {
const self = this;
if (!self.callback) {
console.warn("没有注册回调:", message, error);
}
// 错误回调
self.callback(
protocol.buildMessage("platform::error", { message }, -9999),
error
);
}
/**
* 创建房间
*/
async create(room) {
const self = this;
const response = await self.request(
protocol.buildMessage("room::create", room)
);
return response.body;
}
async enter(roomId) {
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: roomId,
roomId: self.roomId,
})
);
const routerRtpCapabilities = response.body.rtpCapabilities;
await self.mediasoupDevice.load({ routerRtpCapabilities });
await self.request(
protocol.buildMessage("room::enter", {
roomId: roomId,
rtpCapabilities: self.consume
? self.mediasoupDevice.rtpCapabilities
: undefined,
sctpCapabilities:
self.consume && self.useDataChannel
? self.mediasoupDevice.sctpCapabilities
: undefined,
})
);
const routerRtpCapabilities = response.body;
self.mediasoupDevice.load({ routerRtpCapabilities });
self.produceMedia();
}
/**
* TODO共享 navigator.mediaDevices.getDisplayMedia();
* 生产媒体
* TODO验证API试试修改媒体
* audioTrack.getSettings
@@ -426,7 +482,14 @@ class Taoyao {
*/
async produceMedia() {
const self = this;
if (!self.roomId) {
this.callbackError("无效房间");
return;
}
// 检查设备
self.checkDevice();
// 释放资源
self.closeMedia();
// TODO暂时不知道为什么
{
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
@@ -491,15 +554,16 @@ class Taoyao {
"produce",
async ({ kind, appData, rtpParameters }, callback, errback) => {
try {
const { id } = await self.request(
const { producerId } = await self.request(
protocol.buildMessage("media::produce", {
kind,
roomId: self.roomId,
appData,
transportId: self.sendTransport.id,
rtpParameters,
})
);
callback({ id });
callback({ id: producerId });
} catch (error) {
errback(error);
}
@@ -591,10 +655,11 @@ class Taoyao {
}
/**
* 生产音频
* TODO重复点击
*/
async produceAudio() {
const self = this;
if (this.produceAudio && this.mediasoupDevice.canProduce("audio")) {
if (self.audioProduce && self.mediasoupDevice.canProduce("audio")) {
if (this.audioProducer) {
return;
}
@@ -631,13 +696,13 @@ class Taoyao {
this.closeAudioProducer().catch(() => {});
});
} catch (error) {
console.error("打开麦克风异常", error);
self.callbackError("麦克风打开异常", error);
if (track) {
track.stop();
}
}
} else {
console.warn("音频打开失败");
self.callbackError("麦克风打开失败");
}
}
async closeAudioProducer() {
@@ -689,6 +754,7 @@ class Taoyao {
/**
* 生产视频
* TODO重复点击
*/
async produceVideo() {}
/**
@@ -719,15 +785,40 @@ class Taoyao {
}
});
if (!audioEnabled && self.audioProduce) {
throw new Error("没有音频媒体设备");
self.callbackError("没有音频媒体设备");
}
if (!videoEnabled && self.videoProduce) {
throw new Error("没有视频媒体设备");
self.callbackError("没有视频媒体设备");
}
} else {
throw new Error("没有媒体设备");
self.callbackError("没有媒体设备");
}
}
async restartIce() {
const self = this;
try {
if (self.sendTransport) {
const response = await self.request("media::ice::restart", {
roomId: self.roomId,
transportId: self.sendTransport.id,
});
const iceParameters = response.data.iceParameters;
await self.sendTransport.restartIce({ iceParameters });
}
if (self.recvTransport) {
const response = await self.request("media::ice::restart", {
roomId: self.roomId,
transportId: self.recvTransport.id,
});
const iceParameters = response.data.iceParameters;
await self.recvTransport.restartIce({ iceParameters });
}
} catch (error) {
self.callbackError("重启ICE失败", error);
}
}
/**
* 关闭媒体
*/