commit list
This commit is contained in:
commit
7703a266bf
185
README.md
Normal file
185
README.md
Normal file
@ -0,0 +1,185 @@
|
||||
# WebRTC 音视频通话应用
|
||||
|
||||
一个基于WebRTC的实时音视频通话web应用,使用WebRTC技术传输视频流。用户开始通话后会自动播放默认视频流,当用户进行音频或文本对话时会切换到相应的视频流,交互结束后自动回到默认视频流。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎤 **实时音频通话**: 基于WebRTC技术实现点对点音频通话
|
||||
- 📹 **WebRTC视频流传输**: 使用WebRTC技术传输视频流,而非播放本地文件
|
||||
- 📐 **固定视频比例**: 视频播放框固定9:16比例,适合移动端显示
|
||||
- 🎬 **无播放条界面**: 隐藏视频播放条,提供纯净的观看体验
|
||||
- 💬 **文本输入识别**: 根据输入的文本内容切换相应视频流
|
||||
- 🎤 **语音输入识别**: 支持语音输入并切换对应视频流
|
||||
- 🔄 **自动回退**: 交互结束后自动回到默认视频流
|
||||
- 🎨 **现代化UI**: 响应式设计,支持移动端和桌面端
|
||||
- 🔄 **实时同步**: 所有用户实时同步视频流状态
|
||||
|
||||
## WebRTC视频流传输机制
|
||||
|
||||
1. **视频流创建**: 将本地视频文件转换为MediaStream
|
||||
2. **Canvas渲染**: 使用Canvas将视频渲染为实时流
|
||||
3. **流切换**: 根据用户交互动态切换不同的视频流
|
||||
4. **实时同步**: 所有用户实时同步视频流状态
|
||||
5. **缓存机制**: 缓存已创建的视频流,提高切换效率
|
||||
6. **多用户传输**: 通过WebRTC技术向所有连接用户传输视频流
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **前端**: HTML5, CSS3, JavaScript (ES6+)
|
||||
- **后端**: Node.js, Express.js
|
||||
- **实时通信**: Socket.IO
|
||||
- **音视频**: WebRTC API, Canvas API
|
||||
- **视频处理**: MediaStream API
|
||||
|
||||
## 安装和运行
|
||||
|
||||
### 前提条件
|
||||
|
||||
- Node.js (版本 14 或更高)
|
||||
- 现代浏览器 (支持WebRTC和Canvas)
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
# 使用npm安装依赖
|
||||
npm install
|
||||
|
||||
# 或使用yarn安装依赖
|
||||
yarn install
|
||||
```
|
||||
|
||||
### 启动应用
|
||||
|
||||
```bash
|
||||
# 开发模式(自动重启)
|
||||
npm run dev
|
||||
|
||||
# 或生产模式
|
||||
npm start
|
||||
```
|
||||
|
||||
访问 `http://localhost:3000` 开始使用应用。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
new_rtc/
|
||||
├── src/ # 前端源代码
|
||||
│ ├── index.html # 主HTML文件
|
||||
│ ├── index.js # 主JavaScript文件
|
||||
│ └── styles.css # 样式文件
|
||||
├── videos/ # 视频文件目录
|
||||
│ ├── asd.mp4 # 默认视频文件
|
||||
│ ├── zxc.mp4 # 示例视频文件
|
||||
│ └── jkl.mp4 # 示例视频文件
|
||||
├── server.js # Express服务器
|
||||
├── package.json # 项目配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 1. 开始音频通话
|
||||
|
||||
1. 点击"开始音频通话"按钮
|
||||
2. 允许浏览器访问麦克风
|
||||
3. 音频状态将显示"已连接"
|
||||
4. 系统自动开始播放默认视频流(9:16比例,无播放条)
|
||||
|
||||
### 2. 智能视频流切换
|
||||
|
||||
1. **WebRTC视频流**: 使用WebRTC技术传输视频流,而非播放本地文件
|
||||
2. **固定比例显示**: 视频播放框固定9:16比例,适合移动端观看
|
||||
3. **纯净界面**: 隐藏播放条,提供沉浸式观看体验
|
||||
4. **文本输入**: 在文本框中输入内容,系统会根据关键词自动切换视频流
|
||||
5. **语音输入**: 点击语音按钮进行语音输入,系统会识别并切换视频流
|
||||
6. **自动回退**: 交互结束后10秒自动回到默认视频流
|
||||
7. **手动控制**: 点击"回到默认视频"按钮立即回到默认视频流
|
||||
|
||||
### 3. 视频映射配置
|
||||
|
||||
在 `server.js` 中可以配置文本到视频的映射关系:
|
||||
|
||||
```javascript
|
||||
const videoMapping = {
|
||||
'你好': 'asd.mp4',
|
||||
'hello': 'asd.mp4',
|
||||
'再见': 'zxc.mp4',
|
||||
'goodbye': 'zxc.mp4',
|
||||
'谢谢': 'jkl.mp4',
|
||||
'thank you': 'jkl.mp4',
|
||||
'默认': 'asd.mp4'
|
||||
};
|
||||
```
|
||||
|
||||
### 4. 默认视频配置
|
||||
|
||||
在 `server.js` 中可以修改默认视频:
|
||||
|
||||
```javascript
|
||||
const DEFAULT_VIDEO = 'asd.mp4'; // 修改为您的默认视频文件名
|
||||
```
|
||||
|
||||
## 添加新视频
|
||||
|
||||
1. 将视频文件放入 `videos/` 目录
|
||||
2. 支持的格式: MP4, WebM, AVI
|
||||
3. 在 `server.js` 中更新视频映射配置
|
||||
4. 重启服务器
|
||||
|
||||
## 语音识别
|
||||
|
||||
当前版本使用模拟语音识别。要集成真实的语音识别,可以:
|
||||
|
||||
1. 使用Web Speech API
|
||||
2. 集成第三方语音识别服务(如百度、阿里云等)
|
||||
3. 修改 `processVoiceInput` 方法中的语音识别逻辑
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
- Chrome 60+
|
||||
- Firefox 55+
|
||||
- Safari 11+
|
||||
- Edge 79+
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 需要HTTPS环境才能使用麦克风(本地开发除外)
|
||||
2. 确保浏览器允许访问麦克风权限
|
||||
3. 视频文件应放在 `videos/` 目录中
|
||||
4. 建议使用现代浏览器以获得最佳体验
|
||||
5. 交互超时时间可在服务器端配置(默认10秒)
|
||||
6. 视频流使用Canvas渲染,确保浏览器支持Canvas API
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 无法访问麦克风
|
||||
- 检查浏览器权限设置
|
||||
- 确保使用HTTPS或localhost
|
||||
- 检查设备是否被其他应用占用
|
||||
|
||||
### 视频流无法创建或黑屏
|
||||
- 检查视频文件格式是否支持
|
||||
- 确认视频文件路径正确
|
||||
- 检查浏览器是否支持Canvas API
|
||||
- 查看浏览器控制台是否有错误信息
|
||||
- 使用"测试视频文件"按钮检查视频文件是否可访问
|
||||
- 确保视频文件在 `videos/` 目录中存在
|
||||
|
||||
### 连接问题
|
||||
- 检查网络连接
|
||||
- 确认服务器正在运行
|
||||
- 检查防火墙设置
|
||||
|
||||
### CSS样式问题
|
||||
- 确保 `src/styles.css` 文件存在
|
||||
- 检查浏览器控制台是否有404错误
|
||||
- 确认服务器正确配置了静态文件服务
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进这个项目。
|
||||
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "webrtc-video-chat",
|
||||
"version": "1.0.0",
|
||||
"description": "基于WebRTC的音视频通话应用,支持实时播放录制视频",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"socket.io": "^4.7.2",
|
||||
"cors": "^2.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"keywords": ["webrtc", "video", "chat", "realtime"],
|
||||
"author": "Your Name",
|
||||
"license": "MIT"
|
||||
}
|
||||
224
server.js
Normal file
224
server.js
Normal file
@ -0,0 +1,224 @@
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const socketIo = require('socket.io');
|
||||
const cors = require('cors');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketIo(server, {
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"]
|
||||
}
|
||||
});
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('src'));
|
||||
app.use('/videos', express.static('videos'));
|
||||
|
||||
// 存储连接的客户端和他们的视频流状态
|
||||
const connectedClients = new Map();
|
||||
|
||||
// 视频映射配置
|
||||
const videoMapping = {
|
||||
'你好': 'asd.mp4',
|
||||
'hello': 'asd.mp4',
|
||||
'再见': 'zxc.mp4',
|
||||
'goodbye': 'zxc.mp4',
|
||||
'谢谢': 'jkl.mp4',
|
||||
'thank you': 'jkl.mp4',
|
||||
'默认': 'asd.mp4'
|
||||
};
|
||||
|
||||
// 默认视频流配置
|
||||
const DEFAULT_VIDEO = 'asd.mp4';
|
||||
const INTERACTION_TIMEOUT = 10000; // 10秒后回到默认视频
|
||||
|
||||
// 获取视频列表
|
||||
app.get('/api/videos', (req, res) => {
|
||||
const videosDir = path.join(__dirname, 'videos');
|
||||
fs.readdir(videosDir, (err, files) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: '无法读取视频目录' });
|
||||
}
|
||||
const videoFiles = files.filter(file =>
|
||||
file.endsWith('.mp4') || file.endsWith('.webm') || file.endsWith('.avi')
|
||||
);
|
||||
res.json({ videos: videoFiles });
|
||||
});
|
||||
});
|
||||
|
||||
// 获取视频映射
|
||||
app.get('/api/video-mapping', (req, res) => {
|
||||
res.json({ mapping: videoMapping });
|
||||
});
|
||||
|
||||
// 获取默认视频
|
||||
app.get('/api/default-video', (req, res) => {
|
||||
res.json({
|
||||
defaultVideo: DEFAULT_VIDEO,
|
||||
autoLoop: true
|
||||
});
|
||||
});
|
||||
|
||||
// Socket.IO 连接处理
|
||||
io.on('connection', (socket) => {
|
||||
console.log('用户连接:', socket.id);
|
||||
connectedClients.set(socket.id, {
|
||||
socket: socket,
|
||||
currentVideo: DEFAULT_VIDEO,
|
||||
isInInteraction: false
|
||||
});
|
||||
|
||||
// 处理WebRTC信令 - 用于传输视频流
|
||||
socket.on('offer', (data) => {
|
||||
console.log('收到offer:', socket.id);
|
||||
socket.broadcast.emit('offer', {
|
||||
...data,
|
||||
from: socket.id
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('answer', (data) => {
|
||||
console.log('收到answer:', socket.id);
|
||||
socket.broadcast.emit('answer', {
|
||||
...data,
|
||||
from: socket.id
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('ice-candidate', (data) => {
|
||||
console.log('收到ice-candidate:', socket.id);
|
||||
socket.broadcast.emit('ice-candidate', {
|
||||
...data,
|
||||
from: socket.id
|
||||
});
|
||||
});
|
||||
|
||||
// 处理视频流切换请求
|
||||
socket.on('switch-video-stream', (data) => {
|
||||
const { videoFile, type, text } = data;
|
||||
console.log(`用户 ${socket.id} 请求切换视频流: ${videoFile} (${type})`);
|
||||
|
||||
// 更新客户端状态
|
||||
const client = connectedClients.get(socket.id);
|
||||
if (client) {
|
||||
client.currentVideo = videoFile;
|
||||
client.isInInteraction = true;
|
||||
}
|
||||
|
||||
// 广播视频流切换指令给所有用户
|
||||
io.emit('video-stream-switched', {
|
||||
videoFile,
|
||||
type,
|
||||
text,
|
||||
from: socket.id
|
||||
});
|
||||
|
||||
// 如果是交互类型,设置定时器回到默认视频
|
||||
if (type === 'text' || type === 'voice') {
|
||||
setTimeout(() => {
|
||||
console.log(`交互超时,用户 ${socket.id} 回到默认视频`);
|
||||
if (client) {
|
||||
client.currentVideo = DEFAULT_VIDEO;
|
||||
client.isInInteraction = false;
|
||||
}
|
||||
// 广播回到默认视频的指令
|
||||
io.emit('video-stream-switched', {
|
||||
videoFile: DEFAULT_VIDEO,
|
||||
type: 'default',
|
||||
from: socket.id
|
||||
});
|
||||
}, INTERACTION_TIMEOUT);
|
||||
}
|
||||
});
|
||||
|
||||
// 处理通话开始
|
||||
socket.on('call-started', () => {
|
||||
console.log('通话开始,用户:', socket.id);
|
||||
const client = connectedClients.get(socket.id);
|
||||
if (client) {
|
||||
client.currentVideo = DEFAULT_VIDEO;
|
||||
client.isInInteraction = false;
|
||||
}
|
||||
io.emit('call-started', { from: socket.id });
|
||||
});
|
||||
|
||||
// 处理文本输入
|
||||
socket.on('text-input', (data) => {
|
||||
const { text } = data;
|
||||
console.log('收到文本输入:', text, '来自用户:', socket.id);
|
||||
|
||||
// 根据文本查找对应视频
|
||||
let videoFile = videoMapping['默认'];
|
||||
for (const [key, value] of Object.entries(videoMapping)) {
|
||||
if (text.toLowerCase().includes(key.toLowerCase())) {
|
||||
videoFile = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`用户 ${socket.id} 文本输入 "${text}" 对应视频: ${videoFile}`);
|
||||
|
||||
// 发送视频流切换请求
|
||||
socket.emit('switch-video-stream', {
|
||||
videoFile,
|
||||
type: 'text',
|
||||
text
|
||||
});
|
||||
});
|
||||
|
||||
// 处理语音输入
|
||||
socket.on('voice-input', (data) => {
|
||||
const { audioData, text } = data;
|
||||
console.log('收到语音输入:', text, '来自用户:', socket.id);
|
||||
|
||||
// 根据语音识别的文本查找对应视频
|
||||
let videoFile = videoMapping['默认'];
|
||||
for (const [key, value] of Object.entries(videoMapping)) {
|
||||
if (text.toLowerCase().includes(key.toLowerCase())) {
|
||||
videoFile = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`用户 ${socket.id} 语音输入 "${text}" 对应视频: ${videoFile}`);
|
||||
|
||||
// 发送视频流切换请求
|
||||
socket.emit('switch-video-stream', {
|
||||
videoFile,
|
||||
type: 'voice',
|
||||
text
|
||||
});
|
||||
});
|
||||
|
||||
// 处理回到默认视频请求
|
||||
socket.on('return-to-default', () => {
|
||||
console.log('用户请求回到默认视频:', socket.id);
|
||||
const client = connectedClients.get(socket.id);
|
||||
if (client) {
|
||||
client.currentVideo = DEFAULT_VIDEO;
|
||||
client.isInInteraction = false;
|
||||
}
|
||||
socket.emit('switch-video-stream', {
|
||||
videoFile: DEFAULT_VIDEO,
|
||||
type: 'default'
|
||||
});
|
||||
});
|
||||
|
||||
// 断开连接
|
||||
socket.on('disconnect', () => {
|
||||
console.log('用户断开连接:', socket.id);
|
||||
connectedClients.delete(socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`服务器运行在端口 ${PORT}`);
|
||||
console.log(`访问 http://localhost:${PORT} 开始使用`);
|
||||
});
|
||||
82
src/index.html
Normal file
82
src/index.html
Normal file
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebRTC 音频通话</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>WebRTC 音频通话</h1>
|
||||
<p>实时播放录制视频,支持文本和语音输入</p>
|
||||
</header>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 音频状态显示 -->
|
||||
<div class="audio-status">
|
||||
<div class="status-indicator">
|
||||
<span id="audioStatus">未连接</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 录制视频播放区域 -->
|
||||
<div class="recorded-video-section">
|
||||
<h3>录制视频播放</h3>
|
||||
<video id="recordedVideo" autoplay muted>
|
||||
<source src="" type="video/mp4">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
<div class="video-info">
|
||||
<span id="currentVideoName">未选择视频</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制按钮 -->
|
||||
<div class="controls">
|
||||
<button id="startButton" class="btn btn-primary">开始音频通话</button>
|
||||
<button id="stopButton" class="btn btn-danger" disabled>停止通话</button>
|
||||
<button id="muteButton" class="btn btn-secondary">静音</button>
|
||||
<button id="defaultVideoButton" class="btn btn-info">回到默认视频</button>
|
||||
<button id="testVideoButton" class="btn btn-warning">测试视频文件</button>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="input-section">
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="textInput" placeholder="输入文本内容..." />
|
||||
<button id="sendTextButton" class="btn btn-primary">发送文本</button>
|
||||
</div>
|
||||
|
||||
<div class="voice-input-group">
|
||||
<button id="startVoiceButton" class="btn btn-success">开始语音输入</button>
|
||||
<button id="stopVoiceButton" class="btn btn-warning" disabled>停止语音输入</button>
|
||||
<span id="voiceStatus">点击开始语音输入</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频选择 -->
|
||||
<div class="video-selection">
|
||||
<h3>选择要播放的视频</h3>
|
||||
<div id="videoList" class="video-list">
|
||||
<!-- 视频列表将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态显示 -->
|
||||
<div class="status-section">
|
||||
<div id="connectionStatus" class="status">未连接</div>
|
||||
<div id="messageLog" class="message-log"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的视频元素,用于WebRTC连接 -->
|
||||
<video id="localVideo" autoplay muted playsinline style="display: none;"></video>
|
||||
<video id="remoteVideo" autoplay playsinline style="display: none;"></video>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
767
src/index.js
Normal file
767
src/index.js
Normal file
@ -0,0 +1,767 @@
|
||||
// WebRTC 音视频通话应用
|
||||
class WebRTCChat {
|
||||
constructor() {
|
||||
this.socket = null;
|
||||
this.localStream = null;
|
||||
this.peerConnection = null;
|
||||
this.isRecording = false;
|
||||
this.mediaRecorder = null;
|
||||
this.audioChunks = [];
|
||||
this.videoMapping = {};
|
||||
this.defaultVideo = 'asd.mp4';
|
||||
this.currentVideo = null;
|
||||
this.videoStreams = new Map(); // 存储不同视频的MediaStream
|
||||
this.currentVideoStream = null;
|
||||
|
||||
this.initializeElements();
|
||||
this.initializeSocket();
|
||||
this.loadVideoMapping();
|
||||
this.loadVideoList();
|
||||
this.loadDefaultVideo();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
initializeElements() {
|
||||
// 视频元素
|
||||
this.localVideo = document.getElementById('localVideo');
|
||||
this.remoteVideo = document.getElementById('remoteVideo');
|
||||
this.recordedVideo = document.getElementById('recordedVideo');
|
||||
|
||||
// 音频状态元素
|
||||
this.audioStatus = document.getElementById('audioStatus');
|
||||
|
||||
// 按钮元素
|
||||
this.startButton = document.getElementById('startButton');
|
||||
this.stopButton = document.getElementById('stopButton');
|
||||
this.muteButton = document.getElementById('muteButton');
|
||||
this.sendTextButton = document.getElementById('sendTextButton');
|
||||
this.startVoiceButton = document.getElementById('startVoiceButton');
|
||||
this.stopVoiceButton = document.getElementById('stopVoiceButton');
|
||||
this.defaultVideoButton = document.getElementById('defaultVideoButton');
|
||||
this.testVideoButton = document.getElementById('testVideoButton'); // 新增测试按钮
|
||||
|
||||
// 输入元素
|
||||
this.textInput = document.getElementById('textInput');
|
||||
this.voiceStatus = document.getElementById('voiceStatus');
|
||||
|
||||
// 状态元素
|
||||
this.connectionStatus = document.getElementById('connectionStatus');
|
||||
this.messageLog = document.getElementById('messageLog');
|
||||
this.currentVideoName = document.getElementById('currentVideoName');
|
||||
this.videoList = document.getElementById('videoList');
|
||||
}
|
||||
|
||||
initializeSocket() {
|
||||
this.socket = io();
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
this.updateStatus('已连接到服务器', 'connected');
|
||||
this.logMessage('已连接到服务器', 'success');
|
||||
});
|
||||
|
||||
this.socket.on('disconnect', () => {
|
||||
this.updateStatus('与服务器断开连接', 'disconnected');
|
||||
this.logMessage('与服务器断开连接', 'error');
|
||||
});
|
||||
|
||||
// WebRTC 信令处理
|
||||
this.socket.on('offer', (data) => {
|
||||
this.handleOffer(data);
|
||||
});
|
||||
|
||||
this.socket.on('answer', (data) => {
|
||||
this.handleAnswer(data);
|
||||
});
|
||||
|
||||
this.socket.on('ice-candidate', (data) => {
|
||||
this.handleIceCandidate(data);
|
||||
});
|
||||
|
||||
// 视频流切换处理
|
||||
this.socket.on('video-stream-switched', (data) => {
|
||||
this.logMessage(`收到视频流切换指令: ${data.videoFile} (${data.type}) 来自用户: ${data.from}`, 'info');
|
||||
this.switchVideoStream(data.videoFile, data.type, data.text);
|
||||
});
|
||||
|
||||
// 通话开始处理
|
||||
this.socket.on('call-started', (data) => {
|
||||
this.logMessage('通话已开始', 'success');
|
||||
this.startDefaultVideoStream();
|
||||
});
|
||||
}
|
||||
|
||||
async loadVideoMapping() {
|
||||
try {
|
||||
const response = await fetch('/api/video-mapping');
|
||||
const data = await response.json();
|
||||
this.videoMapping = data.mapping;
|
||||
this.logMessage('视频映射加载成功', 'success');
|
||||
} catch (error) {
|
||||
this.logMessage('加载视频映射失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async loadDefaultVideo() {
|
||||
try {
|
||||
const response = await fetch('/api/default-video');
|
||||
const data = await response.json();
|
||||
this.defaultVideo = data.defaultVideo;
|
||||
this.logMessage('默认视频配置加载成功', 'success');
|
||||
} catch (error) {
|
||||
this.logMessage('加载默认视频配置失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async loadVideoList() {
|
||||
try {
|
||||
const response = await fetch('/api/videos');
|
||||
const data = await response.json();
|
||||
this.renderVideoList(data.videos);
|
||||
this.logMessage('视频列表加载成功', 'success');
|
||||
} catch (error) {
|
||||
this.logMessage('加载视频列表失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
renderVideoList(videos) {
|
||||
this.videoList.innerHTML = '';
|
||||
videos.forEach(video => {
|
||||
const videoItem = document.createElement('div');
|
||||
videoItem.className = 'video-item';
|
||||
videoItem.textContent = video;
|
||||
videoItem.onclick = () => this.selectVideo(video);
|
||||
this.videoList.appendChild(videoItem);
|
||||
});
|
||||
}
|
||||
|
||||
selectVideo(videoFile) {
|
||||
// 移除之前的active类
|
||||
document.querySelectorAll('.video-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
|
||||
// 添加active类到选中的视频
|
||||
event.target.classList.add('active');
|
||||
|
||||
// 切换到选中的视频流
|
||||
this.switchVideoStream(videoFile, 'manual');
|
||||
|
||||
// 通知服务器切换视频流
|
||||
this.socket.emit('switch-video-stream', {
|
||||
videoFile,
|
||||
type: 'manual'
|
||||
});
|
||||
}
|
||||
|
||||
async startDefaultVideoStream() {
|
||||
try {
|
||||
this.logMessage('开始创建默认视频流', 'info');
|
||||
|
||||
// 添加加载状态
|
||||
this.recordedVideo.classList.add('loading');
|
||||
|
||||
// 创建默认视频的MediaStream
|
||||
const defaultStream = await this.createVideoStream(this.defaultVideo);
|
||||
|
||||
// 等待流稳定
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 检查流是否有效
|
||||
if (!defaultStream || defaultStream.getTracks().length === 0) {
|
||||
throw new Error('默认视频流创建失败');
|
||||
}
|
||||
|
||||
// 设置视频流
|
||||
this.currentVideoStream = defaultStream;
|
||||
this.recordedVideo.srcObject = defaultStream;
|
||||
this.currentVideo = this.defaultVideo;
|
||||
this.currentVideoName.textContent = `默认视频: ${this.defaultVideo}`;
|
||||
|
||||
// 等待视频元素准备就绪
|
||||
await new Promise(resolve => {
|
||||
const checkReady = () => {
|
||||
if (this.recordedVideo.readyState >= 2) { // HAVE_CURRENT_DATA
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkReady, 100);
|
||||
}
|
||||
};
|
||||
checkReady();
|
||||
});
|
||||
|
||||
// 确保视频开始播放
|
||||
try {
|
||||
await this.recordedVideo.play();
|
||||
this.logMessage('默认视频开始播放', 'success');
|
||||
|
||||
// 移除加载状态,添加播放状态
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
this.recordedVideo.classList.add('playing');
|
||||
} catch (playError) {
|
||||
this.logMessage(`默认视频播放失败: ${playError.message}`, 'error');
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
}
|
||||
|
||||
this.logMessage('默认视频流创建成功', 'success');
|
||||
} catch (error) {
|
||||
this.logMessage('创建默认视频流失败: ' + error.message, 'error');
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
async testVideoFile(videoFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const testVideo = document.createElement('video');
|
||||
testVideo.src = `/videos/${videoFile}`;
|
||||
testVideo.muted = true;
|
||||
|
||||
testVideo.onloadedmetadata = () => {
|
||||
this.logMessage(`视频文件测试成功: ${videoFile} (${testVideo.videoWidth}x${testVideo.videoHeight})`, 'success');
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
testVideo.onerror = () => {
|
||||
this.logMessage(`视频文件测试失败: ${videoFile}`, 'error');
|
||||
reject(new Error(`视频文件不存在或无法加载: ${videoFile}`));
|
||||
};
|
||||
|
||||
// 设置超时
|
||||
setTimeout(() => {
|
||||
reject(new Error(`视频文件加载超时: ${videoFile}`));
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
async createVideoStream(videoFile) {
|
||||
// 如果已经缓存了这个视频流,直接返回
|
||||
if (this.videoStreams.has(videoFile)) {
|
||||
this.logMessage(`使用缓存的视频流: ${videoFile}`, 'info');
|
||||
return this.videoStreams.get(videoFile);
|
||||
}
|
||||
|
||||
try {
|
||||
this.logMessage(`开始创建视频流: ${videoFile}`, 'info');
|
||||
|
||||
// 先测试视频文件是否存在
|
||||
await this.testVideoFile(videoFile);
|
||||
|
||||
// 创建video元素来加载视频
|
||||
const video = document.createElement('video');
|
||||
video.src = `/videos/${videoFile}`;
|
||||
video.muted = true;
|
||||
video.loop = true;
|
||||
video.autoplay = true;
|
||||
video.crossOrigin = 'anonymous'; // 添加跨域支持
|
||||
video.playsInline = true; // 添加playsInline属性
|
||||
|
||||
// 等待视频加载完成
|
||||
await new Promise((resolve, reject) => {
|
||||
video.onloadedmetadata = () => {
|
||||
this.logMessage(`视频元数据加载完成: ${videoFile}`, 'info');
|
||||
resolve();
|
||||
};
|
||||
video.onerror = (error) => {
|
||||
this.logMessage(`视频加载失败: ${videoFile}`, 'error');
|
||||
reject(error);
|
||||
};
|
||||
video.onloadstart = () => {
|
||||
this.logMessage(`开始加载视频: ${videoFile}`, 'info');
|
||||
};
|
||||
video.oncanplay = () => {
|
||||
this.logMessage(`视频可以播放: ${videoFile}`, 'info');
|
||||
};
|
||||
});
|
||||
|
||||
// 创建MediaStream
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 设置canvas尺寸为视频尺寸
|
||||
canvas.width = video.videoWidth || 640;
|
||||
canvas.height = video.videoHeight || 480;
|
||||
|
||||
this.logMessage(`Canvas尺寸: ${canvas.width}x${canvas.height}`, 'info');
|
||||
|
||||
// 开始播放视频
|
||||
try {
|
||||
await video.play();
|
||||
this.logMessage(`视频开始播放: ${videoFile}`, 'info');
|
||||
} catch (playError) {
|
||||
this.logMessage(`视频播放失败: ${playError.message}`, 'error');
|
||||
// 即使播放失败也继续创建流
|
||||
}
|
||||
|
||||
// 等待视频开始播放
|
||||
await new Promise(resolve => {
|
||||
const checkPlay = () => {
|
||||
if (video.readyState >= video.HAVE_CURRENT_DATA) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkPlay, 100);
|
||||
}
|
||||
};
|
||||
checkPlay();
|
||||
});
|
||||
|
||||
// 绘制视频到canvas
|
||||
let isDrawing = false;
|
||||
const drawFrame = () => {
|
||||
if (video.readyState >= video.HAVE_CURRENT_DATA && !isDrawing) {
|
||||
isDrawing = true;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
isDrawing = false;
|
||||
}
|
||||
requestAnimationFrame(drawFrame);
|
||||
};
|
||||
|
||||
// 开始绘制帧
|
||||
drawFrame();
|
||||
|
||||
// 从canvas创建MediaStream
|
||||
const stream = canvas.captureStream(30); // 30fps
|
||||
|
||||
// 等待流创建完成并稳定
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 500); // 给更多时间让流稳定
|
||||
});
|
||||
|
||||
this.logMessage(`视频流创建成功: ${videoFile}`, 'success');
|
||||
|
||||
// 缓存这个视频流
|
||||
this.videoStreams.set(videoFile, stream);
|
||||
|
||||
return stream;
|
||||
} catch (error) {
|
||||
this.logMessage(`创建视频流失败 ${videoFile}: ${error.message}`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async switchVideoStream(videoFile, type = '', text = '') {
|
||||
try {
|
||||
this.logMessage(`开始切换视频流: ${videoFile} (${type})`, 'info');
|
||||
|
||||
// 添加加载状态
|
||||
this.recordedVideo.classList.add('loading');
|
||||
|
||||
// 先创建新的视频流,不立即停止旧的
|
||||
const newStream = await this.createVideoStream(videoFile);
|
||||
|
||||
// 等待流稳定
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
// 检查流是否有效
|
||||
if (!newStream || newStream.getTracks().length === 0) {
|
||||
throw new Error('创建的视频流无效');
|
||||
}
|
||||
|
||||
// 先设置新的视频流,再停止旧的
|
||||
this.currentVideoStream = newStream;
|
||||
this.recordedVideo.srcObject = newStream;
|
||||
this.currentVideo = videoFile;
|
||||
|
||||
// 确保视频开始播放
|
||||
try {
|
||||
await this.recordedVideo.play();
|
||||
this.logMessage('视频元素开始播放', 'info');
|
||||
|
||||
// 移除加载状态,添加播放状态
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
this.recordedVideo.classList.add('playing');
|
||||
} catch (playError) {
|
||||
this.logMessage(`视频播放失败: ${playError.message}`, 'error');
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
}
|
||||
|
||||
// 现在停止旧的视频流
|
||||
if (this.currentVideoStream !== newStream) {
|
||||
const oldStream = this.currentVideoStream;
|
||||
setTimeout(() => {
|
||||
if (oldStream) {
|
||||
oldStream.getTracks().forEach(track => {
|
||||
track.stop();
|
||||
this.logMessage(`已停止旧轨道: ${track.kind}`, 'info');
|
||||
});
|
||||
}
|
||||
}, 1000); // 延迟1秒停止旧流,确保新流已经稳定
|
||||
}
|
||||
|
||||
if (text) {
|
||||
this.currentVideoName.textContent = `交互视频: ${videoFile} (${type}: ${text})`;
|
||||
this.logMessage(`成功切换到交互视频流: ${videoFile} (${type}: ${text})`, 'success');
|
||||
} else {
|
||||
this.currentVideoName.textContent = `视频流: ${videoFile}`;
|
||||
this.logMessage(`成功切换到视频流: ${videoFile}`, 'success');
|
||||
}
|
||||
|
||||
// 检查切换后的状态
|
||||
setTimeout(() => {
|
||||
this.checkVideoStreamStatus();
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
this.logMessage(`切换视频流失败: ${error.message}`, 'error');
|
||||
this.recordedVideo.classList.remove('loading');
|
||||
|
||||
// 如果切换失败,尝试回到默认视频
|
||||
if (videoFile !== this.defaultVideo) {
|
||||
this.logMessage('尝试回到默认视频', 'info');
|
||||
await this.switchVideoStream(this.defaultVideo, 'fallback');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 开始通话按钮
|
||||
this.startButton.onclick = () => this.startCall();
|
||||
|
||||
// 停止通话按钮
|
||||
this.stopButton.onclick = () => this.stopCall();
|
||||
|
||||
// 静音按钮
|
||||
this.muteButton.onclick = () => this.toggleMute();
|
||||
|
||||
// 回到默认视频按钮
|
||||
this.defaultVideoButton.onclick = () => this.returnToDefaultVideo();
|
||||
|
||||
// 测试视频文件按钮
|
||||
this.testVideoButton.onclick = () => this.testAllVideoFiles();
|
||||
|
||||
// 发送文本按钮
|
||||
this.sendTextButton.onclick = () => this.sendText();
|
||||
|
||||
// 回车键发送文本
|
||||
this.textInput.onkeypress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendText();
|
||||
}
|
||||
};
|
||||
|
||||
// 语音输入按钮
|
||||
this.startVoiceButton.onclick = () => this.startVoiceRecording();
|
||||
this.stopVoiceButton.onclick = () => this.stopVoiceRecording();
|
||||
}
|
||||
|
||||
async startCall() {
|
||||
try {
|
||||
this.localStream = await navigator.mediaDevices.getUserMedia({
|
||||
video: false,
|
||||
audio: true
|
||||
});
|
||||
|
||||
this.createPeerConnection();
|
||||
|
||||
this.startButton.disabled = true;
|
||||
this.stopButton.disabled = false;
|
||||
|
||||
this.updateAudioStatus('已连接', 'connected');
|
||||
this.logMessage('音频通话已开始', 'success');
|
||||
|
||||
// 确保视频映射已加载
|
||||
if (Object.keys(this.videoMapping).length === 0) {
|
||||
await this.loadVideoMapping();
|
||||
}
|
||||
|
||||
this.logMessage(`视频映射已加载: ${Object.keys(this.videoMapping).length} 个映射`, 'info');
|
||||
|
||||
// 通知服务器通话开始
|
||||
this.socket.emit('call-started');
|
||||
|
||||
} catch (error) {
|
||||
this.logMessage('无法访问麦克风: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
stopCall() {
|
||||
if (this.localStream) {
|
||||
this.localStream.getTracks().forEach(track => track.stop());
|
||||
this.localStream = null;
|
||||
}
|
||||
|
||||
if (this.peerConnection) {
|
||||
this.peerConnection.close();
|
||||
this.peerConnection = null;
|
||||
}
|
||||
|
||||
// 停止当前视频流
|
||||
if (this.currentVideoStream) {
|
||||
this.currentVideoStream.getTracks().forEach(track => track.stop());
|
||||
this.currentVideoStream = null;
|
||||
}
|
||||
|
||||
this.recordedVideo.srcObject = null;
|
||||
this.currentVideo = null;
|
||||
this.currentVideoName.textContent = '未选择视频';
|
||||
|
||||
this.startButton.disabled = false;
|
||||
this.stopButton.disabled = true;
|
||||
|
||||
this.updateAudioStatus('未连接', 'disconnected');
|
||||
this.logMessage('音频通话已结束', 'info');
|
||||
}
|
||||
|
||||
createPeerConnection() {
|
||||
const configuration = {
|
||||
iceServers: [
|
||||
{ urls: "stun:stun.qq.com:3478" },
|
||||
{ urls: "stun:stun.miwifi.com:3478" },
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||
]
|
||||
};
|
||||
|
||||
this.peerConnection = new RTCPeerConnection(configuration);
|
||||
|
||||
// 添加本地音频流
|
||||
this.localStream.getTracks().forEach(track => {
|
||||
this.peerConnection.addTrack(track, this.localStream);
|
||||
});
|
||||
|
||||
// 处理远程流
|
||||
this.peerConnection.ontrack = (event) => {
|
||||
this.remoteVideo.srcObject = event.streams[0];
|
||||
};
|
||||
|
||||
// 处理ICE候选
|
||||
this.peerConnection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
this.socket.emit('ice-candidate', {
|
||||
candidate: event.candidate
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建并发送offer
|
||||
this.peerConnection.createOffer()
|
||||
.then(offer => this.peerConnection.setLocalDescription(offer))
|
||||
.then(() => {
|
||||
this.socket.emit('offer', {
|
||||
offer: this.peerConnection.localDescription
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.logMessage('创建offer失败: ' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
async handleOffer(data) {
|
||||
if (!this.peerConnection) {
|
||||
await this.startCall();
|
||||
}
|
||||
|
||||
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
|
||||
|
||||
const answer = await this.peerConnection.createAnswer();
|
||||
await this.peerConnection.setLocalDescription(answer);
|
||||
|
||||
this.socket.emit('answer', {
|
||||
answer: this.peerConnection.localDescription
|
||||
});
|
||||
}
|
||||
|
||||
async handleAnswer(data) {
|
||||
if (this.peerConnection) {
|
||||
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
|
||||
}
|
||||
}
|
||||
|
||||
async handleIceCandidate(data) {
|
||||
if (this.peerConnection) {
|
||||
await this.peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
|
||||
}
|
||||
}
|
||||
|
||||
toggleMute() {
|
||||
if (this.localStream) {
|
||||
const audioTrack = this.localStream.getAudioTracks()[0];
|
||||
if (audioTrack) {
|
||||
audioTrack.enabled = !audioTrack.enabled;
|
||||
this.muteButton.textContent = audioTrack.enabled ? '静音' : '取消静音';
|
||||
this.logMessage(audioTrack.enabled ? '已取消静音' : '已静音', 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendText() {
|
||||
const text = this.textInput.value.trim();
|
||||
if (text) {
|
||||
this.socket.emit('text-input', { text });
|
||||
this.logMessage(`发送文本: ${text}`, 'info');
|
||||
this.textInput.value = '';
|
||||
|
||||
// 根据文本查找对应视频并切换
|
||||
this.handleTextInput(text);
|
||||
}
|
||||
}
|
||||
|
||||
async handleTextInput(text) {
|
||||
// 根据文本查找对应视频
|
||||
let videoFile = this.videoMapping['默认'] || this.defaultVideo;
|
||||
for (const [key, value] of Object.entries(this.videoMapping)) {
|
||||
if (text.toLowerCase().includes(key.toLowerCase())) {
|
||||
videoFile = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到对应的视频流
|
||||
await this.switchVideoStream(videoFile, 'text', text);
|
||||
|
||||
// 通知服务器切换视频流
|
||||
this.socket.emit('switch-video-stream', {
|
||||
videoFile,
|
||||
type: 'text',
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
async startVoiceRecording() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
this.audioChunks = [];
|
||||
|
||||
this.mediaRecorder.ondataavailable = (event) => {
|
||||
this.audioChunks.push(event.data);
|
||||
};
|
||||
|
||||
this.mediaRecorder.onstop = () => {
|
||||
const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
|
||||
this.processVoiceInput(audioBlob);
|
||||
};
|
||||
|
||||
this.mediaRecorder.start();
|
||||
this.isRecording = true;
|
||||
|
||||
this.startVoiceButton.disabled = true;
|
||||
this.stopVoiceButton.disabled = false;
|
||||
this.voiceStatus.textContent = '正在录音...';
|
||||
this.startVoiceButton.classList.add('recording');
|
||||
|
||||
this.logMessage('开始语音录制', 'info');
|
||||
} catch (error) {
|
||||
this.logMessage('无法访问麦克风: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
stopVoiceRecording() {
|
||||
if (this.mediaRecorder && this.isRecording) {
|
||||
this.mediaRecorder.stop();
|
||||
this.isRecording = false;
|
||||
|
||||
this.startVoiceButton.disabled = false;
|
||||
this.stopVoiceButton.disabled = true;
|
||||
this.voiceStatus.textContent = '点击开始语音输入';
|
||||
this.startVoiceButton.classList.remove('recording');
|
||||
|
||||
this.logMessage('停止语音录制', 'info');
|
||||
}
|
||||
}
|
||||
|
||||
async processVoiceInput(audioBlob) {
|
||||
// 这里可以集成语音识别API,如Web Speech API或第三方服务
|
||||
// 为了演示,我们使用一个简单的模拟识别
|
||||
const mockText = this.simulateSpeechRecognition();
|
||||
|
||||
this.socket.emit('voice-input', {
|
||||
audioData: audioBlob,
|
||||
text: mockText
|
||||
});
|
||||
|
||||
this.logMessage(`语音识别结果: ${mockText}`, 'info');
|
||||
|
||||
// 根据语音识别结果切换视频流
|
||||
await this.handleVoiceInput(mockText);
|
||||
}
|
||||
|
||||
async handleVoiceInput(text) {
|
||||
// 根据文本查找对应视频
|
||||
let videoFile = this.videoMapping['默认'] || this.defaultVideo;
|
||||
for (const [key, value] of Object.entries(this.videoMapping)) {
|
||||
if (text.toLowerCase().includes(key.toLowerCase())) {
|
||||
videoFile = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到对应的视频流
|
||||
await this.switchVideoStream(videoFile, 'voice', text);
|
||||
|
||||
// 通知服务器切换视频流
|
||||
this.socket.emit('switch-video-stream', {
|
||||
videoFile,
|
||||
type: 'voice',
|
||||
text
|
||||
});
|
||||
}
|
||||
|
||||
simulateSpeechRecognition() {
|
||||
// 模拟语音识别,随机返回预设的文本
|
||||
const texts = ['你好', '再见', '谢谢', 'hello', 'goodbye', 'thank you'];
|
||||
return texts[Math.floor(Math.random() * texts.length)];
|
||||
}
|
||||
|
||||
returnToDefaultVideo() {
|
||||
this.switchVideoStream(this.defaultVideo, 'default');
|
||||
this.socket.emit('return-to-default');
|
||||
this.logMessage(`已返回至默认视频: ${this.defaultVideo}`, 'info');
|
||||
}
|
||||
|
||||
updateStatus(message, type) {
|
||||
this.connectionStatus.textContent = message;
|
||||
this.connectionStatus.className = `status ${type}`;
|
||||
}
|
||||
|
||||
updateAudioStatus(message, type) {
|
||||
this.audioStatus.textContent = message;
|
||||
this.audioStatus.className = `status-indicator ${type}`;
|
||||
}
|
||||
|
||||
logMessage(message, type = 'info') {
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = type;
|
||||
logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
||||
|
||||
this.messageLog.appendChild(logEntry);
|
||||
this.messageLog.scrollTop = this.messageLog.scrollHeight;
|
||||
}
|
||||
|
||||
checkVideoStreamStatus() {
|
||||
const status = {
|
||||
hasStream: !!this.currentVideoStream,
|
||||
streamTracks: this.currentVideoStream ? this.currentVideoStream.getTracks().length : 0,
|
||||
videoReadyState: this.recordedVideo.readyState,
|
||||
videoPaused: this.recordedVideo.paused,
|
||||
videoCurrentTime: this.recordedVideo.currentTime,
|
||||
videoDuration: this.recordedVideo.duration,
|
||||
currentVideo: this.currentVideo
|
||||
};
|
||||
|
||||
this.logMessage(`视频流状态: ${JSON.stringify(status)}`, 'info');
|
||||
return status;
|
||||
}
|
||||
|
||||
async testAllVideoFiles() {
|
||||
this.logMessage('开始测试所有视频文件...', 'info');
|
||||
|
||||
const videoFiles = ['asd.mp4', 'zxc.mp4', 'jkl.mp4'];
|
||||
|
||||
for (const videoFile of videoFiles) {
|
||||
try {
|
||||
await this.testVideoFile(videoFile);
|
||||
} catch (error) {
|
||||
this.logMessage(`视频文件 ${videoFile} 测试失败: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
this.logMessage('视频文件测试完成', 'info');
|
||||
|
||||
// 检查当前视频流状态
|
||||
this.checkVideoStreamStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化应用
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new WebRTCChat();
|
||||
});
|
||||
437
src/styles.css
Normal file
437
src/styles.css
Normal file
@ -0,0 +1,437 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 视频区域 */
|
||||
.video-section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.video-container video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-label {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 音频状态显示 */
|
||||
.audio-status {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-indicator.connected {
|
||||
color: #90EE90;
|
||||
}
|
||||
|
||||
.status-indicator.disconnected {
|
||||
color: #FFB6C1;
|
||||
}
|
||||
|
||||
/* 录制视频区域 */
|
||||
.recorded-video-section {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recorded-video-section h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#recordedVideo {
|
||||
width: 100%;
|
||||
max-width: 400px; /* 限制最大宽度 */
|
||||
aspect-ratio: 9/16; /* 固定9:16比例 */
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
object-fit: cover; /* 确保视频填充容器 */
|
||||
background: #000; /* 视频背景色 */
|
||||
transition: opacity 0.3s ease; /* 添加透明度过渡效果 */
|
||||
}
|
||||
|
||||
/* 视频加载时的样式 */
|
||||
#recordedVideo.loading {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* 视频播放时的样式 */
|
||||
#recordedVideo.playing {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 隐藏视频播放条 */
|
||||
#recordedVideo::-webkit-media-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-panel {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-play-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-timeline {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-current-time-display {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-time-remaining-display {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-mute-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-volume-slider {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#recordedVideo::-webkit-media-controls-fullscreen-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Firefox 隐藏播放条 */
|
||||
#recordedVideo::-moz-media-controls {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 通用隐藏播放条 */
|
||||
#recordedVideo {
|
||||
pointer-events: none; /* 禁用鼠标事件,防止用户点击播放条 */
|
||||
}
|
||||
|
||||
.video-info {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 控制按钮 */
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: #c82333;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background: #545b62;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background: #1e7e34;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover:not(:disabled) {
|
||||
background: #e0a800;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-info:hover:not(:disabled) {
|
||||
background: #138496;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 输入区域 */
|
||||
.input-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.text-input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#textInput {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
#textInput:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.voice-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#voiceStatus {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 视频选择 */
|
||||
.video-selection {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.video-selection h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.video-item {
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video-item:hover {
|
||||
border-color: #007bff;
|
||||
background: #e3f2fd;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.video-item.active {
|
||||
border-color: #007bff;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 状态显示 */
|
||||
.status-section {
|
||||
border-top: 2px solid #eee;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status.connected {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status.disconnected {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.message-log {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.message-log div {
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.message-log .info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.message-log .error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.message-log .success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.video-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text-input-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.voice-input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.video-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.recording {
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
BIN
videos/asd.mp4
Normal file
BIN
videos/asd.mp4
Normal file
Binary file not shown.
BIN
videos/jkl.mp4
Normal file
BIN
videos/jkl.mp4
Normal file
Binary file not shown.
BIN
videos/zxc.mp4
Normal file
BIN
videos/zxc.mp4
Normal file
Binary file not shown.
802
yarn.lock
Normal file
802
yarn.lock
Normal file
@ -0,0 +1,802 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
|
||||
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
|
||||
|
||||
"@types/cors@^2.8.12":
|
||||
version "2.8.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
|
||||
integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@>=10.0.0":
|
||||
version "24.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.1.0.tgz#0993f7dc31ab5cc402d112315b463e383d68a49c"
|
||||
integrity sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==
|
||||
dependencies:
|
||||
undici-types "~7.8.0"
|
||||
|
||||
accepts@~1.3.4, accepts@~1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||
dependencies:
|
||||
mime-types "~2.1.34"
|
||||
negotiator "0.6.3"
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
|
||||
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64id@2.0.0, base64id@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
|
||||
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||
|
||||
body-parser@1.20.3:
|
||||
version "1.20.3"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
|
||||
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
content-type "~1.0.5"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
destroy "1.2.0"
|
||||
http-errors "2.0.0"
|
||||
iconv-lite "0.4.24"
|
||||
on-finished "2.4.1"
|
||||
qs "6.13.0"
|
||||
raw-body "2.5.2"
|
||||
type-is "~1.6.18"
|
||||
unpipe "1.0.0"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
|
||||
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
braces@~3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
bytes@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||
|
||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
|
||||
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
function-bind "^1.1.2"
|
||||
|
||||
call-bound@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
|
||||
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.2"
|
||||
get-intrinsic "^1.3.0"
|
||||
|
||||
chokidar@^3.5.2:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
content-disposition@0.5.4:
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
|
||||
dependencies:
|
||||
safe-buffer "5.2.1"
|
||||
|
||||
content-type@~1.0.4, content-type@~1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||
|
||||
cookie@0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
|
||||
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
|
||||
|
||||
cookie@~0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
||||
|
||||
cors@^2.8.5, cors@~2.8.5:
|
||||
version "2.8.5"
|
||||
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
|
||||
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
|
||||
dependencies:
|
||||
object-assign "^4"
|
||||
vary "^1"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^4:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
|
||||
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
|
||||
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
depd@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
|
||||
destroy@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
|
||||
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||
|
||||
dunder-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
|
||||
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
gopd "^1.2.0"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
||||
|
||||
encodeurl@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
|
||||
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
|
||||
|
||||
engine.io-parser@~5.2.1:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
|
||||
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==
|
||||
|
||||
engine.io@~6.6.0:
|
||||
version "6.6.4"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.4.tgz#0a89a3e6b6c1d4b0c2a2a637495e7c149ec8d8ee"
|
||||
integrity sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==
|
||||
dependencies:
|
||||
"@types/cors" "^2.8.12"
|
||||
"@types/node" ">=10.0.0"
|
||||
accepts "~1.3.4"
|
||||
base64id "2.0.0"
|
||||
cookie "~0.7.2"
|
||||
cors "~2.8.5"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.2.1"
|
||||
ws "~8.17.1"
|
||||
|
||||
es-define-property@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
|
||||
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
|
||||
|
||||
es-errors@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||
|
||||
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
|
||||
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||
|
||||
express@^4.18.2:
|
||||
version "4.21.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32"
|
||||
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
|
||||
dependencies:
|
||||
accepts "~1.3.8"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.20.3"
|
||||
content-disposition "0.5.4"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.7.1"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
encodeurl "~2.0.0"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "1.3.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "2.0.0"
|
||||
merge-descriptors "1.0.3"
|
||||
methods "~1.1.2"
|
||||
on-finished "2.4.1"
|
||||
parseurl "~1.3.3"
|
||||
path-to-regexp "0.1.12"
|
||||
proxy-addr "~2.0.7"
|
||||
qs "6.13.0"
|
||||
range-parser "~1.2.1"
|
||||
safe-buffer "5.2.1"
|
||||
send "0.19.0"
|
||||
serve-static "1.16.2"
|
||||
setprototypeof "1.2.0"
|
||||
statuses "2.0.1"
|
||||
type-is "~1.6.18"
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
finalhandler@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
|
||||
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~2.0.0"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "2.4.1"
|
||||
parseurl "~1.3.3"
|
||||
statuses "2.0.1"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
get-intrinsic@^1.2.5, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
|
||||
dependencies:
|
||||
call-bind-apply-helpers "^1.0.2"
|
||||
es-define-property "^1.0.1"
|
||||
es-errors "^1.3.0"
|
||||
es-object-atoms "^1.1.1"
|
||||
function-bind "^1.1.2"
|
||||
get-proto "^1.0.1"
|
||||
gopd "^1.2.0"
|
||||
has-symbols "^1.1.0"
|
||||
hasown "^2.0.2"
|
||||
math-intrinsics "^1.1.0"
|
||||
|
||||
get-proto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
|
||||
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
|
||||
dependencies:
|
||||
dunder-proto "^1.0.1"
|
||||
es-object-atoms "^1.0.0"
|
||||
|
||||
glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
gopd@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
|
||||
|
||||
has-symbols@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
|
||||
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
|
||||
|
||||
hasown@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
http-errors@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
|
||||
dependencies:
|
||||
depd "2.0.0"
|
||||
inherits "2.0.4"
|
||||
setprototypeof "1.2.0"
|
||||
statuses "2.0.1"
|
||||
toidentifier "1.0.1"
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ignore-by-default@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
|
||||
|
||||
inherits@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
|
||||
|
||||
is-glob@^4.0.1, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
math-intrinsics@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
|
||||
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
|
||||
|
||||
merge-descriptors@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
||||
|
||||
mime-db@1.52.0:
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@~2.1.24, mime-types@~2.1.34:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||
|
||||
ms@2.1.3, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
negotiator@0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
nodemon@^3.0.1:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1"
|
||||
integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==
|
||||
dependencies:
|
||||
chokidar "^3.5.2"
|
||||
debug "^4"
|
||||
ignore-by-default "^1.0.1"
|
||||
minimatch "^3.1.2"
|
||||
pstree.remy "^1.1.8"
|
||||
semver "^7.5.3"
|
||||
simple-update-notifier "^2.0.0"
|
||||
supports-color "^5.5.0"
|
||||
touch "^3.1.0"
|
||||
undefsafe "^2.0.5"
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
object-assign@^4:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
object-inspect@^1.13.3:
|
||||
version "1.13.4"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
|
||||
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
|
||||
|
||||
on-finished@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
path-to-regexp@0.1.12:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
|
||||
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
proxy-addr@~2.0.7:
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
||||
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
|
||||
dependencies:
|
||||
forwarded "0.2.0"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
pstree.remy@^1.1.8:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
|
||||
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
|
||||
|
||||
qs@6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
|
||||
dependencies:
|
||||
side-channel "^1.0.6"
|
||||
|
||||
range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
raw-body@2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
|
||||
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
|
||||
dependencies:
|
||||
bytes "3.1.2"
|
||||
http-errors "2.0.0"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
semver@^7.5.3:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
send@0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
|
||||
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "2.0.0"
|
||||
destroy "1.2.0"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "2.0.0"
|
||||
mime "1.6.0"
|
||||
ms "2.1.3"
|
||||
on-finished "2.4.1"
|
||||
range-parser "~1.2.1"
|
||||
statuses "2.0.1"
|
||||
|
||||
serve-static@1.16.2:
|
||||
version "1.16.2"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
|
||||
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
|
||||
dependencies:
|
||||
encodeurl "~2.0.0"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.3"
|
||||
send "0.19.0"
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||
|
||||
side-channel-list@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
|
||||
integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
object-inspect "^1.13.3"
|
||||
|
||||
side-channel-map@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
|
||||
integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
|
||||
dependencies:
|
||||
call-bound "^1.0.2"
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.5"
|
||||
object-inspect "^1.13.3"
|
||||
|
||||
side-channel-weakmap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
|
||||
integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
|
||||
dependencies:
|
||||
call-bound "^1.0.2"
|
||||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.5"
|
||||
object-inspect "^1.13.3"
|
||||
side-channel-map "^1.0.1"
|
||||
|
||||
side-channel@^1.0.6:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
|
||||
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
|
||||
dependencies:
|
||||
es-errors "^1.3.0"
|
||||
object-inspect "^1.13.3"
|
||||
side-channel-list "^1.0.0"
|
||||
side-channel-map "^1.0.1"
|
||||
side-channel-weakmap "^1.0.2"
|
||||
|
||||
simple-update-notifier@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
|
||||
integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==
|
||||
dependencies:
|
||||
semver "^7.5.3"
|
||||
|
||||
socket.io-adapter@~2.5.2:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082"
|
||||
integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==
|
||||
dependencies:
|
||||
debug "~4.3.4"
|
||||
ws "~8.17.1"
|
||||
|
||||
socket.io-parser@~4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
|
||||
integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
socket.io@^4.7.2:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a"
|
||||
integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==
|
||||
dependencies:
|
||||
accepts "~1.3.4"
|
||||
base64id "~2.0.0"
|
||||
cors "~2.8.5"
|
||||
debug "~4.3.2"
|
||||
engine.io "~6.6.0"
|
||||
socket.io-adapter "~2.5.2"
|
||||
socket.io-parser "~4.2.4"
|
||||
|
||||
statuses@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||
|
||||
supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
toidentifier@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
touch@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694"
|
||||
integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==
|
||||
|
||||
type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
dependencies:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
undefsafe@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
|
||||
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
|
||||
|
||||
undici-types@~7.8.0:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294"
|
||||
integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||
|
||||
vary@^1, vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
ws@~8.17.1:
|
||||
version "8.17.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
|
||||
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==
|
||||
Loading…
x
Reference in New Issue
Block a user