[*] 文件共享

This commit is contained in:
acgist
2023-07-11 08:59:11 +08:00
parent 5811976de8
commit 6b51c5044a
3 changed files with 202 additions and 111 deletions

View File

@@ -2,8 +2,6 @@ const os = require("os");
/** /**
* 桃夭媒体服务地址 * 桃夭媒体服务地址
* 一半配置本机IP地址用于Mediasoup媒体协商时SDP地址信息。
* 如果存在多网卡或者多子网时,需要配置信令地址重写和防火墙端口转发。
*/ */
const defaultTaoyaoHost = "192.168.1.110"; const defaultTaoyaoHost = "192.168.1.110";

View File

@@ -4,12 +4,12 @@
<!-- 终端设置 --> <!-- 终端设置 -->
<el-dialog center width="30%" title="终端设置" v-model="signalVisible" :show-close="false"> <el-dialog center width="30%" title="终端设置" v-model="signalVisible" :show-close="false">
<el-form ref="SignalSetting"> <el-form ref="SignalSetting">
<el-form-item label="终端标识">
<el-input v-model="config.clientId" placeholder="终端标识" />
</el-form-item>
<el-form-item label="终端名称"> <el-form-item label="终端名称">
<el-input v-model="config.name" placeholder="终端名称" /> <el-input v-model="config.name" placeholder="终端名称" />
</el-form-item> </el-form-item>
<el-form-item label="终端标识">
<el-input v-model="config.clientId" placeholder="终端标识" />
</el-form-item>
<el-form-item label="信令地址"> <el-form-item label="信令地址">
<el-input v-model="config.host" placeholder="信令地址" /> <el-input v-model="config.host" placeholder="信令地址" />
</el-form-item> </el-form-item>
@@ -111,10 +111,10 @@ export default {
data() { data() {
return { return {
room: { room: {
// 房间ID
roomId : null,
// 房间名称 // 房间名称
name : null, name : null,
// 房间ID
roomId : null,
// 房间密码 // 房间密码
password : null, password : null,
// 监控终端ID // 监控终端ID
@@ -131,8 +131,8 @@ export default {
// 终端列表 // 终端列表
clients: null, clients: null,
config: { config: {
name : "桃夭",
clientId: "taoyao", clientId: "taoyao",
name : "taoyao",
host : "localhost", host : "localhost",
port : 8888, port : 8888,
username: "taoyao", username: "taoyao",
@@ -154,6 +154,7 @@ export default {
methods: { methods: {
async connectSignal() { async connectSignal() {
this.taoyao = new Taoyao({ ...this.config }); this.taoyao = new Taoyao({ ...this.config });
// this.taoyao = new Taoyao({ ...this.config, fileVideo: video标签对象, videoSource: "file" });
await this.taoyao.connectSignal(this.callback); await this.taoyao.connectSignal(this.callback);
this.signalVisible = false; this.signalVisible = false;
this.remoteClients = this.taoyao.remoteClients; this.remoteClients = this.taoyao.remoteClients;
@@ -211,7 +212,11 @@ export default {
me.roomVisible = true; me.roomVisible = true;
break; break;
case "platform::error": case "platform::error":
console.error(`发生${error ? "异常" : "错误"}`, response, error); if (error) {
console.error("发生异常", response, error);
} else {
console.warn("发生错误", response);
}
ElMessage({ ElMessage({
type : "error", type : "error",
message: message, message: message,

View File

@@ -248,6 +248,8 @@ class Session {
id; id;
// 是否关闭 // 是否关闭
closed; closed;
// 代理对象
proxy;
// 音量 // 音量
volume; volume;
// 远程终端名称 // 远程终端名称
@@ -407,7 +409,7 @@ class Session {
if(this.closed) { if(this.closed) {
return; return;
} }
if(index >= 8) { if(index >= 32) {
console.debug("添加媒体协商次数超限", candidate, index); console.debug("添加媒体协商次数超限", candidate, index);
return; return;
} }
@@ -429,21 +431,22 @@ class Session {
} }
}; };
// TODO:continue
/** /**
* 远程终端 * 远程终端
*/ */
class RemoteClient { class RemoteClient {
// 终端ID
id;
// 是否关闭
closed;
// 代理对象
proxy;
// 音量
volume;
// 终端名称 // 终端名称
name; name;
// 终端标识 // 终端标识
clientId; clientId;
// 音量
volume = "100%";
// 代理对象
proxy;
// 数据消费者 // 数据消费者
dataConsumer; dataConsumer;
// 音频消费者 // 音频消费者
@@ -463,7 +466,10 @@ class RemoteClient {
name, name,
clientId, clientId,
}) { }) {
this.id = clientId;
this.closed = false;
this.name = name; this.name = name;
this.volume = "100%";
this.clientId = clientId; this.clientId = clientId;
} }
@@ -473,29 +479,48 @@ class RemoteClient {
* @param {*} volume 音量 * @param {*} volume 音量
*/ */
setVolume(volume) { setVolume(volume) {
this.volume = ((volume + 127) / 127 * 100) + "%"; const me = this;
} me.volume = ((volume + 127) / 127 * 100) + "%";
close() {
if(this.audioTrack) {
this.audioTrack.stop();
this.audioTrack = null;
}
if(this.videoTrack) {
this.videoTrack.stop();
this.videoTrack = null;
}
}
} }
/** /**
* 桃夭 * 关闭媒体
*/
close() {
const me = this;
if(me.closed) {
return;
}
me.closed = true;
if(me.audioTrack) {
me.audioTrack.stop();
me.audioTrack = null;
}
if(me.videoTrack) {
me.videoTrack.stop();
me.videoTrack = null;
}
if(me.dataConsumer) {
me.dataConsumer.close();
me.dataConsumer = null;
}
if(me.audioConsumer) {
me.audioConsumer.close();
me.audioConsumer = null;
}
if(me.videoConsumer) {
me.videoConsumer.close();
me.videoConsumer = null;
}
}
}
/**
* 桃夭终端
*/ */
class Taoyao extends RemoteClient { class Taoyao extends RemoteClient {
// 信令连接 // 信令连接
connect = false; connect;
// 信令地址 // 信令地址
host; host;
// 信令端口 // 信令端口
@@ -528,8 +553,10 @@ class Taoyao extends RemoteClient {
recvTransport; recvTransport;
// 媒体设备 // 媒体设备
mediasoupDevice; mediasoupDevice;
// 文件共享
fileVideo;
// 视频来源file|camera|screen // 视频来源file|camera|screen
videoSource = "camera"; videoSource;
// 强制使用TCP // 强制使用TCP
forceTcp; forceTcp;
// 强制使用VP8 // 强制使用VP8
@@ -538,7 +565,7 @@ class Taoyao extends RemoteClient {
forceVP9; forceVP9;
// 强制使用H264 // 强制使用H264
forceH264; forceH264;
// 同时上送多种质量媒体 // 多种质量媒体
useLayers; useLayers;
// 是否消费数据 // 是否消费数据
dataConsume; dataConsume;
@@ -571,6 +598,7 @@ class Taoyao extends RemoteClient {
// 本地录像数据 // 本地录像数据
mediaRecorderChunks = []; mediaRecorderChunks = [];
// TODO默认关闭data通道
constructor({ constructor({
name, name,
clientId, clientId,
@@ -578,18 +606,23 @@ class Taoyao extends RemoteClient {
port, port,
username, username,
password, password,
roomId, roomId = null,
// TODO修改默认关闭
dataConsume = true, dataConsume = true,
audioConsume = true, audioConsume = true,
videoConsume = true, videoConsume = true,
// TODO修改默认关闭
dataProduce = true, dataProduce = true,
audioProduce = true, audioProduce = true,
videoProduce = true, videoProduce = true,
fileVideo = null,
videoSource = "camera",
forceTcp = false, forceTcp = false,
forceVP8 = false,
forceVP9 = false,
forceH264 = false,
useLayers = false,
}) { }) {
super({ name, clientId }); super({ name, clientId });
this.connect = false;
this.name = name; this.name = name;
this.clientId = clientId; this.clientId = clientId;
this.host = host; this.host = host;
@@ -603,7 +636,13 @@ class Taoyao extends RemoteClient {
this.dataProduce = dataProduce; this.dataProduce = dataProduce;
this.audioProduce = audioProduce; this.audioProduce = audioProduce;
this.videoProduce = videoProduce; this.videoProduce = videoProduce;
this.fileVideo = fileVideo;
this.videoSource = videoSource;
this.forceTcp = forceTcp; this.forceTcp = forceTcp;
this.forceVP8 = forceVP8;
this.forceVP9 = forceVP9;
this.forceH264 = forceH264;
this.useLayers = useLayers;
} }
/** /**
@@ -611,40 +650,41 @@ class Taoyao extends RemoteClient {
* *
* @param {*} callback 回调事件 * @param {*} callback 回调事件
* *
* @returns * @returns Promise<WebSocket>
*/ */
async connectSignal(callback) { async connectSignal(callback) {
const self = this; const me = this;
self.callback = callback; me.callback = callback;
self.signalChannel = signalChannel; signalChannel.taoyao = me;
signalChannel.taoyao = self; return await signalChannel.connect(
return self.signalChannel.connect( `wss://${me.host}:${me.port}/websocket.signal`
`wss://${self.host}:${self.port}/websocket.signal`
); );
} }
/** /**
* 异步请求 * 异步请求
* *
* @param {*} message 消息 * @param {*} message 信令消息
* @param {*} callback 回调 * @param {*} callback 信令回调
*/ */
push(message, callback) { push(message, callback) {
const me = this; const me = this;
const { header, body } = message;
const { id } = header;
// 请求回调 // 请求回调
if (callback) { if (callback) {
me.callbackMapping.set(message.header.id, callback); me.callbackMapping.set(id, callback);
} }
// 发送消息 // 发送消息
try { try {
signalChannel.channel.send(JSON.stringify(message)); signalChannel.channel.send(JSON.stringify(message));
} catch (error) { } catch (error) {
console.error("异步请求异常", message, error); console.error("异步请求异常", message, error);
} }
} }
/** /**
* 同步请求 * 同步请求
* *
* @param {*} message 消息 * @param {*} message 信令消息
* *
* @returns Promise * @returns Promise
*/ */
@@ -673,40 +713,46 @@ class Taoyao extends RemoteClient {
} }
}); });
} }
/************************ 回调 ************************/
/** /**
* 回调策略: * 回调策略:
*
* 1. 如果注册请求回调同时执行结果返回true不再执行后面所有回调。 * 1. 如果注册请求回调同时执行结果返回true不再执行后面所有回调。
* 2. 执行前置回调 * 2. 执行前置回调
* 3. 如果注册全局回调同时执行结果返回true不再执行后面所有回调。 * 3. 如果注册全局回调同时执行结果返回true不再执行后面所有回调。
* 4. 执行后置回调 * 4. 执行后置回调
* *
* @param {*} message 消息 * @param {*} message 信令消息
*/ */
async on(message) { async on(message) {
const me = this; const me = this;
let done = false; const { header, body } = message;
const { id } = header;
// 请求回调 // 请求回调
if (me.callbackMapping.has(message.header.id)) { if (me.callbackMapping.has(id)) {
try { try {
done = me.callbackMapping.get(message.header.id)(message); if(
me.callbackMapping.get(id)(message)
) {
return;
}
} finally { } finally {
me.callbackMapping.delete(message.header.id); me.callbackMapping.delete(id);
} }
} }
// 前置回调 // 前置回调
if (!done) {
await me.preCallback(message); await me.preCallback(message);
}
// 全局回调 // 全局回调
if (!done && me.callback) { if (
done = await me.callback(message); me.callback &&
await me.callback(message)
) {
return;
} }
// 后置回调 // 后置回调
if (!done) {
await me.postCallback(message); await me.postCallback(message);
} }
}
/** /**
* 前置回调 * 前置回调
* *
@@ -714,7 +760,9 @@ class Taoyao extends RemoteClient {
*/ */
async preCallback(message) { async preCallback(message) {
const me = this; const me = this;
switch (message.header.signal) { const { header, body } = message;
const { signal } = header;
switch (signal) {
case "client::config": case "client::config":
me.defaultClientConfig(message); me.defaultClientConfig(message);
break; break;
@@ -732,14 +780,17 @@ class Taoyao extends RemoteClient {
break; break;
} }
} }
/** /**
* 后置回调 * 后置回调
* *
* @param {*} message 消息 * @param {*} message 信令消息
*/ */
async postCallback(message) { async postCallback(message) {
const me = this; const me = this;
switch (message.header.signal) { const { header, body } = message;
const { signal } = header;
switch (signal) {
case "client::reboot": case "client::reboot":
me.defaultClientReboot(message); me.defaultClientReboot(message);
break; break;
@@ -829,7 +880,12 @@ class Taoyao extends RemoteClient {
break; break;
} }
} }
/************************ 管理 ************************/
/**
* @param {*} producerId 生产者ID
*
* @returns 生产者
*/
getProducer(producerId) { getProducer(producerId) {
const me = this; const me = this;
if(me.audioProducer?.id === producerId) { if(me.audioProducer?.id === producerId) {
@@ -842,36 +898,55 @@ class Taoyao extends RemoteClient {
return null; return null;
} }
} }
/**
* @returns 媒体
*/
async getStream() { async getStream() {
const me = this;
let stream; let stream;
const self = this; if (me.videoSource === "file") {
if (self.videoSource === "file") { stream = me.fileVideo.captureStream();
// TODO实现文件分享 } else if (me.videoSource === "camera") {
// const stream = await this._getExternalVideoStream(); console.debug("媒体配置", me.audioConfig, me.videoConfig);
// track = stream.getVideoTracks()[0].clone();
} else if (self.videoSource === "camera") {
// TODO参数
stream = await navigator.mediaDevices.getUserMedia({ stream = await navigator.mediaDevices.getUserMedia({
audio: self.audioConfig, audio: me.audioConfig,
video: self.videoConfig, video: me.videoConfig,
}); });
} else if (self.videoSource === "screen") { } else if (me.videoSource === "screen") {
// 可能没有音频
const audioConfig = {
...me.audioConfig
};
// 删除min/max
delete audioConfig.sampleSize.min;
delete audioConfig.sampleSize.max;
delete audioConfig.sampleRate.min;
delete audioConfig.sampleRate.max;
const videoConfig = {
...this.videoConfig,
...defaultShareScreenConfig
};
// 删除min/max
delete videoConfig.width.min;
delete videoConfig.width.max;
delete videoConfig.height.min;
delete videoConfig.height.max;
delete videoConfig.frameRate.min;
delete videoConfig.frameRate.max;
console.debug("媒体配置", audioConfig, videoConfig);
stream = await navigator.mediaDevices.getDisplayMedia({ stream = await navigator.mediaDevices.getDisplayMedia({
audio: self.audioConfig, audio: audioConfig,
video: { video: videoConfig,
cursor: true,
width: { max: 1920 },
height: { max: 1080 },
frameRate: { max: 30 },
logicalSurface: true,
displaySurface: "monitor",
},
}); });
} else { } else {
// TODO异常 console.warn("不支持的视频来源", me.videoSource);
} }
return stream; return stream;
} }
// TODOcontinue
async getAudioTrack() { async getAudioTrack() {
const self = this; const self = this;
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
@@ -887,6 +962,7 @@ class Taoyao extends RemoteClient {
); );
return track; return track;
} }
async getVideoTrack() { async getVideoTrack() {
let track; let track;
const self = this; const self = this;
@@ -1540,6 +1616,7 @@ class Taoyao extends RemoteClient {
console.info("dataConsumer transportclose", dataConsumer.id); console.info("dataConsumer transportclose", dataConsumer.id);
dataConsumer.close(); dataConsumer.close();
}); });
// TODO绑定remoteclient
dataConsumer.on('open', () => { dataConsumer.on('open', () => {
console.info("dataConsumer open", dataConsumer.id); console.info("dataConsumer open", dataConsumer.id);
window.dataConsumer = dataConsumer; window.dataConsumer = dataConsumer;
@@ -1796,9 +1873,9 @@ class Taoyao extends RemoteClient {
const self = this; const self = this;
if (!self.callback) { if (!self.callback) {
if (error) { if (error) {
console.error("没有注册回调:", message, error); console.error("发生异常", message, error);
} else { } else {
console.warn("没有注册回调:", message); console.warn("发生错误", message);
} }
return; return;
} }
@@ -2565,20 +2642,29 @@ class Taoyao extends RemoteClient {
const localStream = await me.getStream(); const localStream = await me.getStream();
session.localStream = localStream; session.localStream = localStream;
session.peerConnection = peerConnection; session.peerConnection = peerConnection;
if(session.audioEnabled) { if(session.audioEnabled && localStream.getAudioTracks().length >= 0) {
session.localAudioTrack = localStream.getAudioTracks()[0]; session.localAudioTrack = localStream.getAudioTracks()[0];
if(session.localAudioTrack) {
session.localAudioEnabled = true; session.localAudioEnabled = true;
await session.peerConnection.addTrack(session.localAudioTrack, localStream); await session.peerConnection.addTrack(session.localAudioTrack, localStream);
} else { } else {
// TODO如果没有音频默认获取麦克风
session.localAudioEnabled = false; session.localAudioEnabled = false;
} }
if(session.videoEnabled) { } else {
session.localAudioEnabled = false;
}
if(session.videoEnabled && localStream.getVideoTracks().length >= 0) {
session.localVideoTrack = localStream.getVideoTracks()[0]; session.localVideoTrack = localStream.getVideoTracks()[0];
if(session.localVideoTrack) {
session.localVideoEnabled = true; session.localVideoEnabled = true;
await session.peerConnection.addTrack(session.localVideoTrack, localStream); await session.peerConnection.addTrack(session.localVideoTrack, localStream);
} else { } else {
session.localVideoEnabled = false; session.localVideoEnabled = false;
} }
} else {
session.localVideoEnabled = false;
}
return peerConnection; return peerConnection;
} }
@@ -2814,11 +2900,13 @@ class Taoyao extends RemoteClient {
*/ */
closeAll() { closeAll() {
const me = this; const me = this;
if(me.closed) {
return;
}
me.closed = true;
me.closeRoomMedia(); me.closeRoomMedia();
me.closeSessionMedia(); me.closeSessionMedia();
if (me.signalChannel) { signalChannel.close();
me.signalChannel.close();
}
} }
} }