@gogoqiu/tencent-http-server 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gogoqiu-node-http-service +3 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +3 -0
- package/dist/build.js.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +106 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +318 -0
- package/dist/server.js.map +1 -0
- package/dist/serverCmd.d.ts +8 -0
- package/dist/serverCmd.d.ts.map +1 -0
- package/dist/serverCmd.js +44 -0
- package/dist/serverCmd.js.map +1 -0
- package/dist/shell/install.d.ts +5 -0
- package/dist/shell/install.d.ts.map +1 -0
- package/dist/shell/install.js +221 -0
- package/dist/shell/install.js.map +1 -0
- package/dist/shell/postinst.d.ts +2 -0
- package/dist/shell/postinst.d.ts.map +1 -0
- package/dist/shell/postinst.js +4 -0
- package/dist/shell/postinst.js.map +1 -0
- package/dist/shell.d.ts +2 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +249 -0
- package/dist/shell.js.map +1 -0
- package/package.json +82 -0
- package/public/captures1//346/265/213/350/257/225/347/224/250captures +0 -0
- package/public/chat/offer.html +796 -0
- package/public/chat/options.html +58 -0
- package/public/chat/server-info.html +32 -0
- package/public/chat2.html +272 -0
- package/public/chat3.html +246 -0
- package/public/chat4.html +302 -0
- package/public/chat5.html +41 -0
- package/public/formdata.html +41 -0
- package/public/hls-player.htm +41 -0
- package/public/img/back.svg +1 -0
- package/public/img/offline.svg +1 -0
- package/public/img/online.svg +1 -0
- package/public/ip-record.html +28 -0
- package/public/js/encrypt.js +36 -0
- package/public/js/socket.io.min.js +7 -0
- package/public/js/socket.io.min.js.map +1 -0
- package/public/myhost/hostReg.html +35 -0
- package/public/mylog-chat.htm +260 -0
- package/public/mylog3.html +245 -0
- package/public/navbar.css +17 -0
- package/public/readme.txt +3 -0
- package/public/scroll.htm +139 -0
- package/public/ssh-client.html +0 -0
- package/public/upload-file.html +226 -0
- package/public/upload.html +23 -0
- package/public/uploads1/files-1757866537383-447469495.jpg +0 -0
- package/public/uploads1/files-1757867389485-764531720.jpg +0 -0
- package/public/uploads1/files-1757867518311-278635302.jpg +0 -0
- package/public/uploads1/files-1757867629687-688924576.jpg +0 -0
- package/public/uploads1/files-1757868630683-52261917.jpg +0 -0
- package/public/uploads1/files-1757869187061-619427683.jpg +0 -0
- package/public/uploads1/small_files-1757869187061-619427683.jpg +0 -0
- package/public/uploads1//346/265/213/350/257/225/347/224/250upload +0 -0
- package/public/utils.html +57 -0
- package/public/utils.js +161 -0
- package/public/webrtc/rtc-client.html +238 -0
- package/public/webrtc/rtc-file-transfer-client.html +238 -0
- package/public/webrtc/rtc-file-transfer-server.html +453 -0
- package/public/webrtc/rtc-server.html +453 -0
- package/public/webrtc/video-client-input.html +264 -0
- package/public/webrtc/video-server-input.html +312 -0
- package/public/webrtc/webrtc-chat-with-files.html +581 -0
- package/public/webrtc/webrtc-chat.html +367 -0
- package/public/webrtc/webrtc-file-offer.html +88 -0
- package/public/webrtc/webrtc1.html +186 -0
- package/readme.txt +71 -0
- package/views/chat.ejs +53 -0
- package/views/index.ejs +125 -0
- package/views/special-message.ejs +8 -0
- package/views/ssh.ejs +142 -0
- package/views/utils.ejs +0 -0
- package/views/webrtc/client.ejs +203 -0
- package/views/webrtc/server.ejs +365 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>WebRTC 聊天应用</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
theme: {
|
|
12
|
+
extend: {
|
|
13
|
+
colors: {
|
|
14
|
+
primary: '#3B82F6',
|
|
15
|
+
secondary: '#10B981',
|
|
16
|
+
danger: '#EF4444',
|
|
17
|
+
dark: '#1E293B',
|
|
18
|
+
light: '#F8FAFC'
|
|
19
|
+
},
|
|
20
|
+
fontFamily: {
|
|
21
|
+
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
<style type="text/tailwindcss">
|
|
28
|
+
@layer utilities {
|
|
29
|
+
.content-auto {
|
|
30
|
+
content-visibility: auto;
|
|
31
|
+
}
|
|
32
|
+
.scrollbar-hide {
|
|
33
|
+
scrollbar-width: none;
|
|
34
|
+
-ms-overflow-style: none;
|
|
35
|
+
}
|
|
36
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body class="bg-gray-100 min-h-screen font-sans">
|
|
43
|
+
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
|
44
|
+
<header class="text-center mb-8">
|
|
45
|
+
<h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-dark mb-2">WebRTC 聊天应用</h1>
|
|
46
|
+
<p class="text-gray-600">实时音频通话与文本聊天</p>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
<div class="bg-white rounded-xl shadow-lg overflow-hidden">
|
|
50
|
+
<!-- 连接状态区域 -->
|
|
51
|
+
<div class="bg-primary/10 p-4 border-b flex justify-between items-center">
|
|
52
|
+
<div>
|
|
53
|
+
<h2 class="font-semibold text-dark">连接状态</h2>
|
|
54
|
+
<p id="connectionStatus" class="text-sm text-gray-600">未连接</p>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="flex gap-2">
|
|
57
|
+
<button id="startButton" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition flex items-center gap-1">
|
|
58
|
+
<i class="fa fa-play"></i> 开始
|
|
59
|
+
</button>
|
|
60
|
+
<button id="callButton" class="bg-secondary hover:bg-secondary/90 text-white px-4 py-2 rounded-lg transition flex items-center gap-1" disabled>
|
|
61
|
+
<i class="fa fa-phone"></i> 通话
|
|
62
|
+
</button>
|
|
63
|
+
<button id="hangupButton" class="bg-danger hover:bg-danger/90 text-white px-4 py-2 rounded-lg transition flex items-center gap-1" disabled>
|
|
64
|
+
<i class="fa fa-phone-slash"></i> 挂断
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- 媒体区域 -->
|
|
70
|
+
<div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
71
|
+
<div class="bg-gray-100 rounded-lg p-2">
|
|
72
|
+
<h3 class="text-sm font-medium text-gray-600 mb-2">本地视频</h3>
|
|
73
|
+
<video id="localVideo" autoplay muted class="w-full aspect-video object-cover rounded bg-gray-200"></video>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="bg-gray-100 rounded-lg p-2">
|
|
76
|
+
<h3 class="text-sm font-medium text-gray-600 mb-2">远程视频</h3>
|
|
77
|
+
<video id="remoteVideo" autoplay class="w-full aspect-video object-cover rounded bg-gray-200"></video>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- 消息区域 -->
|
|
82
|
+
<div class="border-t p-4">
|
|
83
|
+
<h3 class="text-lg font-semibold text-dark mb-3">聊天消息</h3>
|
|
84
|
+
<div id="messages" class="h-64 overflow-y-auto mb-4 p-3 bg-gray-50 rounded-lg scrollbar-hide"></div>
|
|
85
|
+
<div class="flex gap-2">
|
|
86
|
+
<input type="text" id="messageInput" placeholder="输入消息..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50">
|
|
87
|
+
<button id="sendButton" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition">
|
|
88
|
+
<i class="fa fa-paper-plane"></i> 发送
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- 信令信息区域 -->
|
|
95
|
+
<div class="mt-6 bg-white rounded-xl shadow-lg p-4">
|
|
96
|
+
<h3 class="text-lg font-semibold text-dark mb-3">信令信息 (用于两个标签页之间测试)</h3>
|
|
97
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
98
|
+
<div>
|
|
99
|
+
<label class="block text-sm font-medium text-gray-600 mb-1">本地描述</label>
|
|
100
|
+
<textarea id="localDescription" class="w-full h-24 p-2 border border-gray-300 rounded-lg text-sm overflow-auto scrollbar-hide" readonly></textarea>
|
|
101
|
+
<button id="copyLocalDesc" class="mt-1 text-sm text-primary hover:text-primary/80">复制</button>
|
|
102
|
+
</div>
|
|
103
|
+
<div>
|
|
104
|
+
<label class="block text-sm font-medium text-gray-600 mb-1">远程描述</label>
|
|
105
|
+
<textarea id="remoteDescription" class="w-full h-24 p-2 border border-gray-300 rounded-lg text-sm overflow-auto scrollbar-hide"></textarea>
|
|
106
|
+
<button id="setRemoteDesc" class="mt-1 text-sm text-primary hover:text-primary/80">应用远程描述</button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
// DOM 元素
|
|
114
|
+
const startButton = document.getElementById('startButton');
|
|
115
|
+
const callButton = document.getElementById('callButton');
|
|
116
|
+
const hangupButton = document.getElementById('hangupButton');
|
|
117
|
+
const localVideo = document.getElementById('localVideo');
|
|
118
|
+
const remoteVideo = document.getElementById('remoteVideo');
|
|
119
|
+
const connectionStatus = document.getElementById('connectionStatus');
|
|
120
|
+
const messages = document.getElementById('messages');
|
|
121
|
+
const messageInput = document.getElementById('messageInput');
|
|
122
|
+
const sendButton = document.getElementById('sendButton');
|
|
123
|
+
const localDescription = document.getElementById('localDescription');
|
|
124
|
+
const remoteDescription = document.getElementById('remoteDescription');
|
|
125
|
+
const copyLocalDesc = document.getElementById('copyLocalDesc');
|
|
126
|
+
const setRemoteDesc = document.getElementById('setRemoteDesc');
|
|
127
|
+
|
|
128
|
+
// WebRTC 相关变量
|
|
129
|
+
let localStream;
|
|
130
|
+
let remoteStream;
|
|
131
|
+
let peerConnection;
|
|
132
|
+
let dataChannel;
|
|
133
|
+
|
|
134
|
+
// 配置 STUN 服务器
|
|
135
|
+
const configuration = {
|
|
136
|
+
iceServers: [
|
|
137
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
138
|
+
{ urls: 'stun:stun1.l.google.com:19302' }
|
|
139
|
+
]
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// 状态更新函数
|
|
143
|
+
function updateStatus(text, isError = false) {
|
|
144
|
+
connectionStatus.textContent = text;
|
|
145
|
+
connectionStatus.className = isError ? 'text-sm text-danger' : 'text-sm text-gray-600';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 添加消息到聊天区域
|
|
149
|
+
function addMessage(text, isLocal = false) {
|
|
150
|
+
const messageElement = document.createElement('div');
|
|
151
|
+
messageElement.className = `mb-2 p-2 rounded-lg max-w-[80%] ${isLocal ? 'bg-primary/10 text-dark ml-auto' : 'bg-gray-200 text-dark'}`;
|
|
152
|
+
messageElement.textContent = text;
|
|
153
|
+
messages.appendChild(messageElement);
|
|
154
|
+
messages.scrollTop = messages.scrollHeight;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 开始按钮 - 获取媒体流
|
|
158
|
+
startButton.addEventListener('click', async () => {
|
|
159
|
+
try {
|
|
160
|
+
// 获取用户媒体流 (音频和视频)
|
|
161
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
162
|
+
audio: true,
|
|
163
|
+
video: { width: 640, height: 480 }
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
localStream = stream;
|
|
167
|
+
localVideo.srcObject = stream;
|
|
168
|
+
|
|
169
|
+
// 创建 RTCPeerConnection 实例
|
|
170
|
+
peerConnection = new RTCPeerConnection(configuration);
|
|
171
|
+
|
|
172
|
+
// 创建数据通道用于文本聊天
|
|
173
|
+
dataChannel = peerConnection.createDataChannel('chat');
|
|
174
|
+
setupDataChannel(dataChannel);
|
|
175
|
+
|
|
176
|
+
// 监听远程数据通道
|
|
177
|
+
peerConnection.ondatachannel = (event) => {
|
|
178
|
+
dataChannel = event.channel;
|
|
179
|
+
setupDataChannel(dataChannel);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// 添加本地流到连接
|
|
183
|
+
localStream.getTracks().forEach(track => {
|
|
184
|
+
peerConnection.addTrack(track, localStream);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// 监听远程流
|
|
188
|
+
peerConnection.ontrack = (event) => {
|
|
189
|
+
if (!remoteStream) {
|
|
190
|
+
remoteStream = new MediaStream();
|
|
191
|
+
remoteVideo.srcObject = remoteStream;
|
|
192
|
+
}
|
|
193
|
+
event.streams[0].getTracks().forEach(track => {
|
|
194
|
+
remoteStream.addTrack(track);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// 监听 ICE 候选
|
|
199
|
+
peerConnection.onicecandidate = (event) => {
|
|
200
|
+
if (event.candidate) {
|
|
201
|
+
// 这里简化处理,实际应通过信令服务器发送候选
|
|
202
|
+
console.log('ICE 候选:', event.candidate);
|
|
203
|
+
// 保存到本地存储,用于同一设备测试
|
|
204
|
+
localStorage.setItem('lastIceCandidate', JSON.stringify(event.candidate));
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// 监听连接状态变化
|
|
209
|
+
peerConnection.oniceconnectionstatechange = () => {
|
|
210
|
+
updateStatus(`ICE 连接状态: ${peerConnection.iceConnectionState}`);
|
|
211
|
+
|
|
212
|
+
if (peerConnection.iceConnectionState === 'connected') {
|
|
213
|
+
addMessage('已连接,开始聊天吧!');
|
|
214
|
+
} else if (peerConnection.iceConnectionState === 'failed') {
|
|
215
|
+
updateStatus('连接失败,请尝试重新连接', true);
|
|
216
|
+
} else if (peerConnection.iceConnectionState === 'disconnected') {
|
|
217
|
+
updateStatus('连接已断开', true);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
startButton.disabled = true;
|
|
222
|
+
callButton.disabled = false;
|
|
223
|
+
updateStatus('已准备好,等待建立连接');
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('获取媒体流失败:', error);
|
|
226
|
+
updateStatus(`获取媒体失败: ${error.message}`, true);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 配置数据通道
|
|
231
|
+
function setupDataChannel(channel) {
|
|
232
|
+
channel.onopen = () => {
|
|
233
|
+
updateStatus('数据通道已打开,可以发送消息');
|
|
234
|
+
sendButton.disabled = false;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
channel.onmessage = (event) => {
|
|
238
|
+
addMessage(`对方: ${event.data}`, false);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
channel.onclose = () => {
|
|
242
|
+
updateStatus('数据通道已关闭');
|
|
243
|
+
sendButton.disabled = true;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
channel.onerror = (error) => {
|
|
247
|
+
console.error('数据通道错误:', error);
|
|
248
|
+
updateStatus(`消息发送失败: ${error}`, true);
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 通话按钮 - 创建offer
|
|
253
|
+
callButton.addEventListener('click', async () => {
|
|
254
|
+
try {
|
|
255
|
+
// 创建 offer
|
|
256
|
+
const offer = await peerConnection.createOffer();
|
|
257
|
+
await peerConnection.setLocalDescription(offer);
|
|
258
|
+
|
|
259
|
+
// 显示本地描述
|
|
260
|
+
localDescription.value = JSON.stringify(offer);
|
|
261
|
+
|
|
262
|
+
callButton.disabled = true;
|
|
263
|
+
hangupButton.disabled = false;
|
|
264
|
+
updateStatus('正在等待对方接受连接...');
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error('创建 offer 失败:', error);
|
|
267
|
+
updateStatus(`创建连接失败: ${error.message}`, true);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 挂断按钮
|
|
272
|
+
hangupButton.addEventListener('click', () => {
|
|
273
|
+
// 关闭所有轨道
|
|
274
|
+
if (localStream) {
|
|
275
|
+
localStream.getTracks().forEach(track => track.stop());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (remoteStream) {
|
|
279
|
+
remoteStream.getTracks().forEach(track => track.stop());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 关闭连接
|
|
283
|
+
if (peerConnection) {
|
|
284
|
+
peerConnection.close();
|
|
285
|
+
peerConnection = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 关闭数据通道
|
|
289
|
+
if (dataChannel) {
|
|
290
|
+
dataChannel.close();
|
|
291
|
+
dataChannel = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 重置UI
|
|
295
|
+
localVideo.srcObject = null;
|
|
296
|
+
remoteVideo.srcObject = null;
|
|
297
|
+
localDescription.value = '';
|
|
298
|
+
remoteDescription.value = '';
|
|
299
|
+
startButton.disabled = false;
|
|
300
|
+
callButton.disabled = true;
|
|
301
|
+
hangupButton.disabled = true;
|
|
302
|
+
sendButton.disabled = true;
|
|
303
|
+
updateStatus('已挂断连接');
|
|
304
|
+
addMessage('通话已结束');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// 发送消息
|
|
308
|
+
sendButton.addEventListener('click', () => {
|
|
309
|
+
const message = messageInput.value.trim();
|
|
310
|
+
if (message && dataChannel && dataChannel.readyState === 'open') {
|
|
311
|
+
dataChannel.send(message);
|
|
312
|
+
addMessage(`我: ${message}`, true);
|
|
313
|
+
messageInput.value = '';
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// 按Enter发送消息
|
|
318
|
+
messageInput.addEventListener('keypress', (e) => {
|
|
319
|
+
if (e.key === 'Enter') {
|
|
320
|
+
sendButton.click();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// 复制本地描述
|
|
325
|
+
copyLocalDesc.addEventListener('click', () => {
|
|
326
|
+
localDescription.select();
|
|
327
|
+
document.execCommand('copy');
|
|
328
|
+
alert('本地描述已复制');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// 应用远程描述
|
|
332
|
+
setRemoteDesc.addEventListener('click', async () => {
|
|
333
|
+
try {
|
|
334
|
+
const remoteDesc = JSON.parse(remoteDescription.value);
|
|
335
|
+
await peerConnection.setRemoteDescription(new RTCSessionDescription(remoteDesc));
|
|
336
|
+
|
|
337
|
+
// 如果是offer,创建answer
|
|
338
|
+
if (remoteDesc.type === 'offer') {
|
|
339
|
+
const answer = await peerConnection.createAnswer();
|
|
340
|
+
await peerConnection.setLocalDescription(answer);
|
|
341
|
+
localDescription.value = JSON.stringify(answer);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
updateStatus('已应用远程描述,正在建立连接...');
|
|
345
|
+
|
|
346
|
+
// 检查是否有ICE候选需要添加
|
|
347
|
+
const lastCandidate = localStorage.getItem('lastIceCandidate');
|
|
348
|
+
if (lastCandidate) {
|
|
349
|
+
try {
|
|
350
|
+
await peerConnection.addIceCandidate(JSON.parse(lastCandidate));
|
|
351
|
+
console.log('已添加远程ICE候选');
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('添加ICE候选失败:', error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.error('设置远程描述失败:', error);
|
|
358
|
+
updateStatus(`设置远程描述失败: ${error.message}`, true);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// 初始化状态
|
|
363
|
+
updateStatus('请点击"开始"按钮初始化');
|
|
364
|
+
sendButton.disabled = true;
|
|
365
|
+
</script>
|
|
366
|
+
</body>
|
|
367
|
+
</html>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta name="description" content="WebRTC code samples">
|
|
8
|
+
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
|
|
9
|
+
<meta itemprop="description" content="Client-side WebRTC code samples">
|
|
10
|
+
<meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
|
|
11
|
+
<meta itemprop="name" content="WebRTC code samples">
|
|
12
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
13
|
+
<meta id="theme-color" name="theme-color" content="#ffffff">
|
|
14
|
+
|
|
15
|
+
<base target="_blank">
|
|
16
|
+
|
|
17
|
+
<title>File to peer connection</title>
|
|
18
|
+
|
|
19
|
+
<script src="/js/socket.io.min.js"></script>
|
|
20
|
+
</head>
|
|
21
|
+
|
|
22
|
+
<body>
|
|
23
|
+
|
|
24
|
+
<div id="container">
|
|
25
|
+
|
|
26
|
+
<h1><a href="/"><span>SERVER</span></a></h1>
|
|
27
|
+
|
|
28
|
+
<video id="leftVideo" controls muted crossorigin="anonymous">
|
|
29
|
+
<!-- NB CORS: https://bugzilla.mozilla.org/show_bug.cgi?id=1177793 -->
|
|
30
|
+
<source src="http://git.my.host:28888/200_pages1/mnt/disk2/sd2.3.2.mp4" type="video/mp4" />
|
|
31
|
+
<source src="http://git.my.host:28888/200_pages1/mnt/disk2/sd2.3.1.mp4" type="video/mp4" />
|
|
32
|
+
<p>This browser does not support the video element.</p>
|
|
33
|
+
</video>
|
|
34
|
+
<p>获取stun/turn回掉</p>
|
|
35
|
+
<div>
|
|
36
|
+
使用方法:
|
|
37
|
+
两处分别打开server/client, 服务器端分别点击init/call, 就能实现视频的同步播放。
|
|
38
|
+
</div>
|
|
39
|
+
<button onclick="init()">初始化PeerConnect</button>
|
|
40
|
+
<p>建立offer</p>
|
|
41
|
+
<button onclick="call()">发起请求</button>
|
|
42
|
+
<!--button>播放</button-->
|
|
43
|
+
|
|
44
|
+
<!--video id="rightVideo" autoplay controls></video-->
|
|
45
|
+
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
function initWs(event) {
|
|
50
|
+
let socket = io();
|
|
51
|
+
const hostId = new Date().getTime();
|
|
52
|
+
|
|
53
|
+
socket.on('webrtc ice-candidate', function (msg) {
|
|
54
|
+
//socket.emit('ice-candidate', { host:hostId, candidate: event.candidate})
|
|
55
|
+
//msg.candidate
|
|
56
|
+
if (msg.host != hostId) {
|
|
57
|
+
//pc1.addIceCandidate( msg.candidate )
|
|
58
|
+
(pc1).addIceCandidate(msg.candidate)
|
|
59
|
+
.then(() => {
|
|
60
|
+
console.log('addIceCandidate success', msg.candidate)
|
|
61
|
+
})
|
|
62
|
+
.catch(function (err) {
|
|
63
|
+
console.error('Error adding candidate:' + err)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
console.log(msg)
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
socket.on('webrtc offer', function (msg) {
|
|
70
|
+
console.log(msg)
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
socket.on('webrtc answer', function (desc) {
|
|
74
|
+
console.log('got answer')
|
|
75
|
+
pc1.setRemoteDescription(desc, function () {
|
|
76
|
+
console.log('!!!!!!setRemoteDescription ok');
|
|
77
|
+
}, (error) => console.log('Failed to set session description: ' + error.toString()));
|
|
78
|
+
//pc1.setRemoteDescription(desc)
|
|
79
|
+
console.log(desc)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function initRTC() {
|
|
85
|
+
let pc = new RTCPeerConnection(null);
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
</body>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html>
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
|
|
7
|
+
<meta charset="utf-8">
|
|
8
|
+
<meta name="description" content="WebRTC code samples">
|
|
9
|
+
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
|
|
10
|
+
<meta itemprop="description" content="Client-side WebRTC code samples">
|
|
11
|
+
<meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
|
|
12
|
+
<meta itemprop="name" content="WebRTC code samples">
|
|
13
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
14
|
+
<meta id="theme-color" name="theme-color" content="#ffffff">
|
|
15
|
+
|
|
16
|
+
<base target="_blank">
|
|
17
|
+
|
|
18
|
+
<title>File to peer connection</title>
|
|
19
|
+
|
|
20
|
+
<script src="/js/socket.io.min.js"></script>
|
|
21
|
+
</head>
|
|
22
|
+
|
|
23
|
+
<body>
|
|
24
|
+
|
|
25
|
+
<div id="container">
|
|
26
|
+
|
|
27
|
+
<h1><a href="/"><span>SERVER</span></a></h1>
|
|
28
|
+
|
|
29
|
+
<input type="file" id="fileInput" />
|
|
30
|
+
<p>获取stun/turn回掉</p>
|
|
31
|
+
<div>
|
|
32
|
+
使用方法:
|
|
33
|
+
两处分别打开server/client, 服务器端分别点击init/call, 就能实现视频的同步播放。
|
|
34
|
+
</div>
|
|
35
|
+
<button onclick="init()">初始化PeerConnect</button>
|
|
36
|
+
<p>建立offer</p>
|
|
37
|
+
<button onclick="call()">发起请求</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<script>
|
|
41
|
+
/*
|
|
42
|
+
运行上下文
|
|
43
|
+
*/
|
|
44
|
+
let pc, ws, hostId
|
|
45
|
+
/*
|
|
46
|
+
channel
|
|
47
|
+
*/
|
|
48
|
+
function initWs(isOffer) {
|
|
49
|
+
ws = io();
|
|
50
|
+
hostId = new Date().getTime();
|
|
51
|
+
|
|
52
|
+
ws.on('webrtc ice-candidate', function (msg) {
|
|
53
|
+
//socket.emit('ice-candidate', { host:hostId,
|
|
54
|
+
// candidate: event.candidate})
|
|
55
|
+
//msg.candidate
|
|
56
|
+
if (msg.host != hostId) {
|
|
57
|
+
//pc1.addIceCandidate( msg.candidate )
|
|
58
|
+
// 哪些格式来标记远端的主机
|
|
59
|
+
(pc1).addIceCandidate(msg.candidate)
|
|
60
|
+
.then(() => {
|
|
61
|
+
console.log('addIceCandidate success', msg.candidate)
|
|
62
|
+
})
|
|
63
|
+
.catch(function (err) {
|
|
64
|
+
console.error('Error adding candidate:' + err)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
console.log(msg)
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
ws.on('webrtc offer', function (desc) {
|
|
71
|
+
if (isOffer) {
|
|
72
|
+
// 构建offer的,没有收到offer
|
|
73
|
+
console.log(desc)
|
|
74
|
+
} else {
|
|
75
|
+
console.log('got offer', "offer desc", desc)
|
|
76
|
+
//
|
|
77
|
+
pc.setRemoteDescription(desc, function () {
|
|
78
|
+
// offer description
|
|
79
|
+
console.log('!!!!!!setRemoteDescription ok');
|
|
80
|
+
}, (error) => console.error('Failed to set session description: '
|
|
81
|
+
+ error.toString()));
|
|
82
|
+
//console.log(desc)
|
|
83
|
+
pc.createAnswer((desc) => {
|
|
84
|
+
// answer with client description
|
|
85
|
+
pc.setLocalDescription(desc, function () {
|
|
86
|
+
console.log('!!!!!!setLocalDescription ok');
|
|
87
|
+
}, (error) => console.log('Failed to set session description: '
|
|
88
|
+
+ error.toString())
|
|
89
|
+
);
|
|
90
|
+
// 特定类型的消息
|
|
91
|
+
socket.emit('webrtc answer', desc);
|
|
92
|
+
},
|
|
93
|
+
(error) => console.log('Failed to create session description: '
|
|
94
|
+
+ error.toString()));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
ws.on('webrtc answer', function (desc) {
|
|
99
|
+
if (isOffer) {
|
|
100
|
+
console.log('got answer')
|
|
101
|
+
/*
|
|
102
|
+
setLocalDescription/setRemoteDescription的时机
|
|
103
|
+
*/
|
|
104
|
+
pc.setRemoteDescription(desc, function () {
|
|
105
|
+
console.log('!!!!!!setRemoteDescription ok');
|
|
106
|
+
}, (error) => console.log('Failed to set session description: '
|
|
107
|
+
+ error.toString()));
|
|
108
|
+
//pc1.setRemoteDescription(desc)
|
|
109
|
+
console.log(desc)
|
|
110
|
+
} else {
|
|
111
|
+
console.log(desc)
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// offer端发起offer
|
|
117
|
+
function startOffer( videoElm ) {
|
|
118
|
+
let stream
|
|
119
|
+
if (videoElm.captureStream) {
|
|
120
|
+
// 从video里获取流?媒体流
|
|
121
|
+
stream = videoElm.captureStream();
|
|
122
|
+
}
|
|
123
|
+
stream.getTracks().forEach(
|
|
124
|
+
function (track) {
|
|
125
|
+
pc1.addTrack(
|
|
126
|
+
track,
|
|
127
|
+
stream
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
console.log('pc1 createOffer start');
|
|
132
|
+
//竟然需要等待createOffer发起后,才发起ice请求
|
|
133
|
+
/*
|
|
134
|
+
pc1.onicecandidate
|
|
135
|
+
本地产生onicecandidate事件, 客户端收到webrtc ice-candidate事件
|
|
136
|
+
发送offer信息
|
|
137
|
+
建立offer竟然是异步,通过回调获得终结的事件
|
|
138
|
+
*/
|
|
139
|
+
//pc.createDataChannel('fileTransfer');
|
|
140
|
+
pc.createOffer( (desc) => {
|
|
141
|
+
console.log('Offer from pc1\n' + desc.sdp);
|
|
142
|
+
console.log('pc1 setLocalDescription start');
|
|
143
|
+
pc.setLocalDescription(desc, function() {
|
|
144
|
+
//
|
|
145
|
+
console.log( "!!!!!!SetLocalSuccess ok" );
|
|
146
|
+
}, (err)=>{
|
|
147
|
+
console.error( "SetLocalFailed", err );
|
|
148
|
+
});
|
|
149
|
+
// 发送offer信息
|
|
150
|
+
ws.emit( 'webrtc offer', desc );
|
|
151
|
+
},
|
|
152
|
+
(error) => console.log('Failed to create session description: '
|
|
153
|
+
+ error.toString()),
|
|
154
|
+
offerOptions);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function initRTC( isOffer ) {
|
|
158
|
+
const config = {
|
|
159
|
+
iceServers: [
|
|
160
|
+
{ urls: 'stun:stun.l.google.com:19302' }, // 谷歌公共 STUN 服务器
|
|
161
|
+
{ urls: 'stun:stun1.l.google.com:19302' }
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
// new RTCPeerConnection(null)时,涵盖哪些stun服务器
|
|
165
|
+
pc = new RTCPeerConnection(config);
|
|
166
|
+
pc.onicecandidate = function (e) {
|
|
167
|
+
socket.emit('webrtc ice-candidate',
|
|
168
|
+
{ host: hostId, candidate: (event.candidate) });
|
|
169
|
+
};
|
|
170
|
+
pc.oniceconnectionstatechange = function (e) {
|
|
171
|
+
console.log('ICE state change event: ', e);
|
|
172
|
+
}
|
|
173
|
+
if( isOffer ){
|
|
174
|
+
|
|
175
|
+
}else{
|
|
176
|
+
pc.ontrack = function (e) {
|
|
177
|
+
console.log('Got remote track:', e.streams[0]);
|
|
178
|
+
e.streams[0].getTracks().forEach(function (track) {
|
|
179
|
+
console.log('Add a track to the remoteStream:', track);
|
|
180
|
+
});
|
|
181
|
+
rightVideo.srcObject = e.streams[0];
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
</script>
|
|
186
|
+
</body>
|