[*] 文件共享

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";

View File

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

View File

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