diff --git a/docs/Deploy.md b/docs/Deploy.md index 4929979..bbdf6b3 100644 --- a/docs/Deploy.md +++ b/docs/Deploy.md @@ -629,13 +629,17 @@ openssl pkcs12 -export -clcerts -in server.crt -inkey server.key -out server.p12 ``` # 安装路径 ---prefix=/data/dev/ffmpeg/build +--prefix=/usr/local +--prefix=/usr/local/ffmpeg # 执行文件路径 ---bindir=/data/dev/ffmpeg/bin +--bindir=/usr/local/bin +--bindir=/usr/local/ffmpeg/bin # 库文件路径 --libdir=/usr/local/lib +--libdir=/usr/local/ffmpeg/lib # 头文件路径 --includedir=/usr/local/include +--includedir=/usr/local/ffmpeg/include ``` ## 清理源码 diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java index 34470f6..03d30b6 100644 --- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java +++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/client/SessionClient.java @@ -277,6 +277,7 @@ public class SessionClient extends Client { if(sdp == null || sdpMid == null || sdpMLineIndex == null) { Log.w(SessionClient.class.getSimpleName(), "无效媒体协商:" + body); } else { + // TODO:验证是否可能为空PC this.peerConnection.addIceCandidate(new IceCandidate(sdpMid, sdpMLineIndex, sdp)); } } diff --git a/taoyao-client-media/src/Config.js b/taoyao-client-media/src/Config.js index 5a7bfbf..40f9d2e 100644 --- a/taoyao-client-media/src/Config.js +++ b/taoyao-client-media/src/Config.js @@ -5,7 +5,7 @@ const os = require("os"); * 一半配置本机IP地址,用于Mediasoup媒体协商时SDP地址信息。 * 如果存在多网卡或者多子网时,需要配置信令地址重写和防火墙端口转发。 */ -defaultTaoyaoHost = "192.168.1.110"; +const defaultTaoyaoHost = "192.168.1.110"; /** * 配置 @@ -13,25 +13,27 @@ defaultTaoyaoHost = "192.168.1.110"; module.exports = { // 服务名称 name: "taoyao-client-media", - // 服务配置 + // 信令配置 signal: { - // 服务版本 - version: "1.0.0", + // 信令版本 + version : "1.0.0", // 终端标识 - clientId: "taoyao-client-media", + clientId : "taoyao-client-media", + // 终端类型 + clientType: "MEDIA", // 终端名称 - name: "桃夭媒体服务", + name : "桃夭媒体服务", // 信令地址 - host: "127.0.0.1", - // host: "192.168.1.100", + host : "127.0.0.1", + // host : "192.168.1.100", // 信令端口 - port: 8888, + port : 8888, // 信令协议 - scheme: "wss", + scheme : "wss", // 信令帐号 - username: "taoyao", + username : "taoyao", // 信令密码 - password: "taoyao", + password : "taoyao", }, // 录像配置 record: { diff --git a/taoyao-client-media/src/Taoyao.js b/taoyao-client-media/src/Taoyao.js index 8cee742..a7f4dcf 100644 --- a/taoyao-client-media/src/Taoyao.js +++ b/taoyao-client-media/src/Taoyao.js @@ -92,7 +92,7 @@ const signalChannel = { if (me.heartbeatTimer) { clearTimeout(me.heartbeatTimer); } - me.heartbeatTimer = setTimeout(async function () { + me.heartbeatTimer = setTimeout(async () => { if (me.connected()) { me.push( protocol.buildMessage("client::heartbeat", { @@ -120,7 +120,7 @@ const signalChannel = { * @param {*} address 信令地址 * @param {*} reconnection 是否重连 * - * @returns Promise + * @returns Promise */ async connect(address, reconnection = true) { const me = this; @@ -134,13 +134,13 @@ const signalChannel = { return new Promise((resolve, reject) => { console.debug("连接信令通道", me.address); me.channel = new WebSocket(me.address, { rejectUnauthorized: false, handshakeTimeout: 5000 }); - me.channel.on("open", async function () { + me.channel.on("open", async () => { console.info("打开信令通道", me.address); me.push( protocol.buildMessage("client::register", { name : config.signal.name, clientId : config.signal.clientId, - clientType: "MEDIA", + clientType: config.signal.clientType, username : config.signal.username, password : config.signal.password, // TODO:电池信息 @@ -153,7 +153,7 @@ const signalChannel = { me.heartbeat(); resolve(me.channel); }); - me.channel.on("close", async function () { + me.channel.on("close", async () => { console.warn("信令通道关闭", me.address); me.taoyao.connect = false; if(!me.connected()) { @@ -164,11 +164,11 @@ const signalChannel = { } // 不要失败回调 }); - me.channel.on("error", async function (e) { + me.channel.on("error", async (e) => { console.error("信令通道异常", me.address, e); // 不要失败回调 }); - me.channel.on("message", async function (data) { + me.channel.on("message", async (data) => { const content = data.toString(); try { console.debug("信令通道消息", content); @@ -196,7 +196,7 @@ const signalChannel = { clearTimeout(me.reconnectTimer); } // 定时重连 - me.reconnectTimer = setTimeout(function () { + me.reconnectTimer = setTimeout(() => { console.info("重连信令通道", me.address); me.connect(me.address, me.reconnection); me.lockReconnect = false; @@ -227,9 +227,9 @@ const signalChannel = { console.info("关闭信令通道", me.address); clearTimeout(me.heartbeatTimer); clearTimeout(me.reconnectTimer); - me.reconnection = false; - me.channel.close(); + me.reconnection = false; me.taoyao.connect = false; + me.channel.close(); }, }; diff --git a/taoyao-client-web/src/App.vue b/taoyao-client-web/src/App.vue index 298f89d..b59d48e 100644 --- a/taoyao-client-web/src/App.vue +++ b/taoyao-client-web/src/App.vue @@ -150,12 +150,6 @@ export default { }; }, mounted() { - console.info(` - 中庭地白树栖鸦,冷露无声湿桂花。 - 今夜月明人尽望,不知秋思落谁家。 - - :: https://gitee.com/acgist/taoyao - `); }, methods: { async connectSignal() { diff --git a/taoyao-client-web/src/components/Config.js b/taoyao-client-web/src/components/Config.js index 1808f23..d56ec65 100644 --- a/taoyao-client-web/src/components/Config.js +++ b/taoyao-client-web/src/components/Config.js @@ -1,8 +1,18 @@ /** - * 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 } + * 配置 */ +const config = { + // 信令配置 + signal: { + // 信令版本 + version : "1.0.0", + // 终端类型 + clientType: "WEB", + } +} /** * 音频默认配置 + * 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 } * * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings */ @@ -31,6 +41,7 @@ const defaultAudioConfig = { /** * 视频默认配置 + * 配置:{ min: 8000, exact: 32000, ideal: 32000, max: 48000 } * * https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings */ @@ -147,6 +158,7 @@ const defaultRTCPeerConnectionConfig = { }; export { + config, defaultAudioConfig, defaultVideoConfig, defaultShareScreenConfig, diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js index 7c7f39e..5edfda3 100644 --- a/taoyao-client-web/src/components/Taoyao.js +++ b/taoyao-client-web/src/components/Taoyao.js @@ -1,5 +1,6 @@ import * as mediasoupClient from "mediasoup-client"; import { + config, defaultAudioConfig, defaultVideoConfig, defaultShareScreenConfig, @@ -9,7 +10,7 @@ import { } from "./Config.js"; /** - * 信令 + * 信令协议 */ const protocol = { // 当前索引 @@ -48,7 +49,7 @@ const protocol = { const me = this; const message = { header: { - v : v || "1.0.0", + v : v || config.signal.version, id : id || me.buildId(), signal: signal, }, @@ -97,7 +98,7 @@ const signalChannel = { if (me.heartbeatTimer) { clearTimeout(me.heartbeatTimer); } - me.heartbeatTimer = setTimeout(async function () { + me.heartbeatTimer = setTimeout(async () => { if (me.connected()) { const battery = await navigator.getBattery(); me.push( @@ -125,7 +126,7 @@ const signalChannel = { * @param {*} address 信令地址 * @param {*} reconnection 是否重连 * - * @returns Promise + * @returns Promise */ async connect(address, reconnection = true) { const me = this; @@ -139,14 +140,14 @@ const signalChannel = { return new Promise((resolve, reject) => { console.debug("连接信令通道", me.address); me.channel = new WebSocket(me.address); - me.channel.onopen = async function () { + me.channel.onopen = async () => { console.info("打开信令通道", me.address); const battery = await navigator.getBattery(); me.push( protocol.buildMessage("client::register", { name : me.taoyao.name, clientId : me.taoyao.clientId, - clientType: "WEB", + clientType: config.signal.clientType, username : me.taoyao.username, password : me.taoyao.password, battery : battery.level * 100, @@ -158,7 +159,7 @@ const signalChannel = { me.heartbeat(); resolve(me.channel); }; - me.channel.onclose = async function () { + me.channel.onclose = async () => { console.warn("信令通道关闭", me.channel); me.taoyao.connect = false; if(!me.connected()) { @@ -170,11 +171,11 @@ const signalChannel = { } // 不要失败回调 }; - me.channel.onerror = async function (e) { + me.channel.onerror = async (e) => { console.error("信令通道异常", me.channel, e); // 不要失败回调 }; - me.channel.onmessage = async function (e) { + me.channel.onmessage = async (e) => { const content = e.data; try { console.debug("信令通道消息", content); @@ -202,7 +203,7 @@ const signalChannel = { clearTimeout(me.reconnectTimer); } // 定时重连 - me.reconnectTimer = setTimeout(function () { + me.reconnectTimer = setTimeout(() => { console.info("重连信令通道", me.address); me.connect(me.address, me.reconnection); me.lockReconnect = false; @@ -230,27 +231,27 @@ const signalChannel = { */ close() { const me = this; + console.info("关闭信令通道", me.address); clearTimeout(me.heartbeatTimer); clearTimeout(me.reconnectTimer); - me.reconnection = false; - me.channel.close(); + me.reconnection = false; me.taoyao.connect = false; + me.channel.close(); }, }; /** - * 会话 + * 视频会话 */ class Session { - // 会话ID id; - // 远程终端名称 - name; - // 音量 - volume = "100%"; // 是否关闭 closed; + // 音量 + volume; + // 远程终端名称 + name; // 远程终端ID clientId; // 会话ID @@ -263,17 +264,21 @@ class Session { videoEnabled; // 本地音频 localAudioTrack; + // 本地音频是否可用(暂停关闭) localAudioEnabled; // 本地视频 localVideoTrack; + // 本地视频是否可用(暂停关闭) localVideoEnabled; // 远程音频 remoteAudioTrack; + // 远程音频是否可用(暂停关闭) remoteAudioEnabled; // 远程视频 remoteVideoTrack; + // 远程视频是否可用(暂停关闭) remoteVideoEnabled; - // PeerConnection + // WebRTC PeerConnection peerConnection; constructor({ @@ -283,67 +288,91 @@ class Session { audioEnabled, videoEnabled }) { - this.id = sessionId; - this.name = name; - this.closed = false; - this.clientId = clientId; - this.sessionId = sessionId; + this.id = sessionId; + this.closed = false; + this.volume = "100%"; + this.name = name; + this.clientId = clientId; + this.sessionId = sessionId; this.audioEnabled = audioEnabled; this.videoEnabled = videoEnabled; } + /** + * 暂停本地媒体 + * + * @param {*} type 媒体类型 + */ async pause(type) { if(type === 'audio' && this.localAudioTrack) { - this.localAudioEnabled = false; + this.localAudioEnabled = false; this.localAudioTrack.enabled = false; } if(type === 'video' && this.localVideoTrack) { - this.localVideoEnabled = false; + this.localVideoEnabled = false; this.localVideoTrack.enabled = false; } } + /** + * 恢复本地媒体 + * + * @param {*} type 媒体类型 + */ async resume(type) { if(type === 'audio' && this.localAudioTrack) { - this.localAudioEnabled = true; + this.localAudioEnabled = true; this.localAudioTrack.enabled = true; } if(type === 'video' && this.localVideoTrack) { - this.localVideoEnabled = true; + this.localVideoEnabled = true; this.localVideoTrack.enabled = true; } } + /** + * 暂停远程媒体 + * + * @param {*} type 媒体类型 + */ async pauseRemote(type) { - if(type === 'audio') { - this.remoteAudioEnabled = false; + if(type === 'audio' && this.remoteAudioTrack) { + this.remoteAudioEnabled = false; this.remoteAudioTrack.enabled = false; } - if(type === 'video') { - this.remoteVideoEnabled = false; + if(type === 'video' && this.remoteVideoTrack) { + this.remoteVideoEnabled = false; this.remoteVideoTrack.enabled = false; } } + /** + * 恢复远程媒体 + * + * @param {*} type 媒体类型 + */ async resumeRemote(type) { - if(type === 'audio') { - this.remoteAudioEnabled = true; + if(type === 'audio' && this.remoteAudioTrack) { + this.remoteAudioEnabled = true; this.remoteAudioTrack.enabled = true; } - if(type === 'video') { - this.remoteVideoEnabled = true; + if(type === 'video' && this.remoteVideoTrack) { + this.remoteVideoEnabled = true; this.remoteVideoTrack.enabled = true; } } + /** + * 关闭视频会话 + */ async close() { if(this.closed) { return; } console.debug("会话关闭", this.sessionId); this.closed = true; - this.localAudioEnabled = false; - this.localVideoEnabled = false; + this.localAudioEnabled = false; + this.localVideoEnabled = false; this.remoteAudioEnabled = false; this.remoteVideoEnabled = false; if(this.localAudioTrack) { @@ -368,21 +397,39 @@ class Session { } } - async addIceCandidate(candidate) { + /** + * 添加媒体协商 + * + * @param {*} candidate 媒体协商 + * @param {*} index 重试次数 + */ + async addIceCandidate(candidate, index = 0) { if(this.closed) { return; } - if(!candidate || candidate.sdpMid === undefined || candidate.sdpMLineIndex === undefined && candidate.candidate === undefined) { + if(index >= 8) { + console.debug("添加媒体协商次数超限", candidate, index); + return; + } + if( + !candidate || + candidate.sdpMid === undefined || + candidate.candidate === undefined || + candidate.sdpMLineIndex === undefined + ) { + console.debug("无效媒体协商", candidate); return; } if(this.peerConnection) { await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); } else { - setTimeout(() => this.addIceCandidate(candidate), 50); + console.debug("延迟添加媒体协商", candidate, index); + setTimeout(() => this.addIceCandidate(candidate, ++index), 100); } } +}; -} +// TODO:continue /** * 远程终端 @@ -2694,6 +2741,18 @@ class Taoyao extends RemoteClient { }); } + /** + * 关闭媒体轨道 + * + * @param {*} mediaTrack 媒体轨道 + */ + closeMediaTrack(mediaTrack) { + if(!mediaTrack) { + return; + } + mediaTrack.stop(); + } + /** * 关闭视频房间媒体 */ diff --git a/taoyao-client-web/src/main.js b/taoyao-client-web/src/main.js index 36c00d0..82cbcce 100644 --- a/taoyao-client-web/src/main.js +++ b/taoyao-client-web/src/main.js @@ -1,9 +1,16 @@ -import App from "./App.vue"; +import App from "./App.vue"; +import ElementPlus from "element-plus"; import { createApp } from "vue"; -import ElementPlus from "element-plus"; import "./assets/main.css"; import "element-plus/dist/index.css"; +console.info(` +中庭地白树栖鸦,冷露无声湿桂花。 +今夜月明人尽望,不知秋思落谁家。 + +:: https://gitee.com/acgist/taoyao +`); + const app = createApp(App); app.use(ElementPlus); app.mount("#app"); diff --git a/taoyao-client-web/vite.config.js b/taoyao-client-web/vite.config.js index f88bd79..72d9e46 100644 --- a/taoyao-client-web/vite.config.js +++ b/taoyao-client-web/vite.config.js @@ -1,6 +1,6 @@ -import fs from "node:fs"; -import vue from "@vitejs/plugin-vue"; -import { defineConfig } from "vite"; +import fs from "node:fs"; +import vue from "@vitejs/plugin-vue"; +import { defineConfig } from "vite"; import { fileURLToPath, URL } from "node:url"; export default defineConfig({ @@ -9,8 +9,8 @@ export default defineConfig({ port: 8443, host: "0.0.0.0", https: { + key : fs.readFileSync("src/certs/server.key"), cert: fs.readFileSync("src/certs/server.crt"), - key: fs.readFileSync("src/certs/server.key"), }, }, resolve: {