';
61 | obj = $(obj);
62 | obj.find("p").eq(0).html(msg.msg);
63 | obj.fadeIn('slow').appendTo(console);
64 | scrollToBottom($(console))
65 | };
66 | textClient.adddestMsg = function(msg) {
67 | var console = Console.Win + " #console",
68 | obj = '
' + new Date().toLocaleString() + '
';
69 | obj = $(obj);
70 | obj.find("p").eq(0).html(msg.msg);
71 | obj.fadeIn('slow').appendTo(console);
72 | scrollToBottom($(console))
73 | };
74 | textClient.setRoomInfo = function(roomInfo) {
75 | if (roomInfo == null || typeof(roomInfo) == "undefined") return;
76 | var _str = new StringBuffer();
77 | if (typeof(roomInfo.creater) == "undefined") roomInfo.creater = "神秘用户";
78 | if (typeof(roomInfo.createTime) == "undefined") roomInfo.createTime = new Date().toLocaleString();
79 | _str.append('
' + roomInfo.creater + '');
80 | _str.append('创建于' + roomInfo.createTime);
81 | $(".mwd .pageTop .title").html(_str.toString())
82 | };
83 | textClient.initUserList = function(list) {
84 | var userlist = ".mwd .mode-text .pageRight";
85 | if (list == null || typeof(list) == "undefined" || list.length == 0) {
86 | $(userlist).html('');
87 | return
88 | }
89 | var _str = new StringBuffer();
90 | for (var i = 0; i < list.length; i++) {
91 | _str.append('
');
92 | _str.append('
![' + list[i] + '](pic/websocket/headpic.png)
');
93 | _str.append('
' + list[i] + '');
94 | _str.append('
')
95 | }
96 | $(userlist).html(_str.toString())
97 | };
98 | var videoClient = {
99 | online: false,
100 | initialize: function() {
101 | if(videoClient.online && videoClient.webrtc) return;
102 | webrtc = new SimpleWebRTC({
103 | // the id/element dom element that will hold "our" video
104 | localVideoEl: 'myVideo',
105 | // the id/element dom element that will hold remote videos
106 | remoteVideosEl: '',
107 | // immediately ask for camera access
108 | autoRequestMedia: true,
109 | debug: false,
110 | detectSpeakingEvents: true,
111 | media: {
112 | video: true,
113 | audio: true
114 | },
115 | autoAdjustMic: false
116 | });
117 | webrtc.on('videoAdded', function (video, peer) {
118 | console.log('video added', peer);
119 | $(video).fadeIn('slow').appendTo(Console.Win + " #videocontent");
120 | $(video).attr("id","dest-" + peer.id);
121 | });
122 | webrtc.on('videoRemoved', function (video, peer) {
123 | console.log('video removed ', peer);
124 | var dest = $('video[id="dest-' + peer.id + '"]');
125 | dest && dest.remove()
126 | });
127 | webrtc.joinRoom('video');
128 | videoClient.online = true;
129 | videoClient.webrtc = webrtc;
130 | },
131 | close: function() {
132 | if(videoClient.webrtc) {
133 | videoClient.webrtc.leaveRoom();
134 | }
135 | }
136 | }
137 | var Console = {
138 | Win: ".mwd .mode-text",
139 | ChatMode: MODE_TEXT,
140 | fullScreen: false,
141 | isMin: false,
142 | setMode: function(mode) {
143 | if (Console.ChatMode == mode) {
144 | return
145 | }
146 | Console.ChatMode = mode;
147 | switch (mode) {
148 | case MODE_TEXT:
149 | Console.Win = ".mwd .mode-text";
150 | break;
151 | case MODE_VIDEO:
152 | Console.Win = ".mwd .mode-video";
153 | if (!videoClient.online) {
154 | Console.myVideo = new Video("#myVideo");
155 | videoClient.initialize();
156 | }
157 | break
158 | }
159 | $(Console.Win).siblings("[class^='mode-']").hide();
160 | $(Console.Win).show()
161 | },
162 | log: function(message, error, delay) {
163 | if (message == "") return;
164 | console.log(message);
165 | delay = delay || 10000;
166 | var v = $(Console.Win).find(".edit .buttons .info");
167 | v.html(message);
168 | v.attr("title", message);
169 | if (error) v.addClass("error");
170 | setTimeout(function() {
171 | v.removeClass("error").html("")
172 | }, 5000)
173 | },
174 | resize: function() {
175 | var padding = parseInt($(Console.Win).find(".pageRight").css("padding-left"));
176 | $(Console.Win).find(".pageLeft").width(parseInt($(".mwd").width() - $(Console.Win).find(".pageRight").width() - padding * 2));
177 | $(".content").height(parseInt($(".mwd").height() - $(".pageTop").height() - $(".edit").height() - 19))
178 | },
179 | toggleFullScreen: function() {
180 | Console.fullScreen = !Console.fullScreen;
181 | if (Console.fullScreen) {
182 | $(".mwd").addClass("mwd_full");
183 | $("h2").hide()
184 | } else {
185 | $(".mwd").removeClass("mwd_full");
186 | $("h2").show()
187 | }
188 | Console.resize()
189 | },
190 | toggleMin: function() {
191 | Console.isMin = !Console.isMin;
192 | if (Console.isMin) {
193 | $(".mwd").fadeOut('quick');
194 | $("#min-max").fadeIn('quick')
195 | } else {
196 | $("#min-max").fadeOut('quick');
197 | $(".mwd").fadeIn('quick')
198 | }
199 | },
200 | minToMax: function() {
201 | Console.toggleMin();
202 | Console.isMin = false;
203 | if (!Console.fullScreen) Console.toggleFullScreen()
204 | },
205 | close: function() {
206 | textClient.online && textClient.close();
207 | videoClient.online && videoClient.close();
208 | CloseWindow()
209 | }
210 | };
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/js/media.js:
--------------------------------------------------------------------------------
1 | (function(window) {
2 | window.URL = window.URL || window.webkitURL || window.msURL || window.oURL;
3 |
4 | window.Media = function(option) {
5 |
6 | var type = 0; // 0.无,1.音频,2.视频,3.音视频
7 | if (option.video && option.audio) {
8 | type = 3;
9 | } else if (option.video) {
10 | type = 2;
11 | } else if (option.audio) {
12 | type = 1;
13 | }
14 |
15 | jAlert = function(msg, title, callback) {
16 | alert(msg);
17 | callback && callback();
18 | };
19 | sucCallBack = function(media, stream) {
20 | media.localMediaStream = stream;
21 | };
22 |
23 | errCallBack = function(error) {
24 | if (error.PERMISSION_DENIED) {
25 | jAlert('您拒绝了浏览器请求媒体的权限', '提示');
26 | } else if (error.NOT_SUPPORTED_ERROR) {
27 | jAlert('对不起,您的浏览器不支持摄像头/麦克风的API,请使用其他浏览器', '提示');
28 | } else if (error.MANDATORY_UNSATISFIED_ERROR) {
29 | jAlert('指定的媒体类型未接收到媒体流', '提示');
30 | } else {
31 | jAlert('相关硬件正在被其他程序使用中', '提示');
32 | }
33 | };
34 |
35 | this.source = function() {
36 | if (type < 2) {
37 | return null;
38 | }
39 | var stream = this.localMediaStream;
40 | return (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(stream) : stream;
41 | }
42 |
43 | this.start = function(success, error) {
44 | var _media = this;
45 | if (this.localMediaStream.readyState) {
46 | success && success();
47 | return;
48 | }
49 | navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
50 | var userAgent = navigator.userAgent,
51 | msgTitle = '提示',
52 | notSupport = '对不起,您的浏览器不支持摄像头/麦克风的API,请使用其他浏览器',
53 | message = "为了获得更准确的测试结果,请尽量将面部置于红框中,然后进行拍摄、扫描。 点击“OK”后,请在屏幕上方出现的提示框选择“允许”,以开启摄像功能";
54 | try {
55 | if (navigator.getUserMedia) {
56 | if (userAgent.indexOf('MQQBrowser') > -1) {
57 | errCallBack({
58 | NOT_SUPPORTED_ERROR: 1
59 | });
60 | return false;
61 | }
62 | navigator.getUserMedia(option, function(stream) {
63 | sucCallBack(_media, stream);
64 | success && success();
65 | }, function(err) {
66 | errCallBack(err);
67 | error && error();
68 | });
69 | } else {
70 | /*
71 | if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Oupeng") == -1 && userAgent.indexOf("360 Aphone") == -1) {
72 |
73 | } //判断是否Safari浏览器
74 | */
75 | errCallBack({
76 | NOT_SUPPORTED_ERROR: 1
77 | });
78 | return false;
79 | }
80 | } catch (err) {
81 | errCallBack({
82 | NOT_SUPPORTED_ERROR: 1
83 | });
84 | return false;
85 | }
86 | return true;
87 | }
88 |
89 | this.stop = function(url) {
90 | url && window.URL && window.URL.revokeObjectURL && window.URL.revokeObjectURL(url);
91 | this.localMediaStream.readyState && this.localMediaStream.stop();
92 | this.localMediaStream = new Object();
93 | return true;
94 | };
95 |
96 | this.localMediaStream = new Object();
97 | }
98 |
99 | window.Camera = function() {
100 |
101 | this.start = function(success, error) {
102 | return this.media.start(success, error);
103 | };
104 |
105 | this.stop = function(url) {
106 | return this.media.stop(url);
107 | };
108 |
109 | this.source = function() {
110 | return this.media.source();
111 | };
112 |
113 | this.media = new Media({
114 | video: true
115 | });
116 | }
117 |
118 | window.MicroPhone = function() {
119 |
120 | this.start = function(success, error) {
121 | var _microphone = this;
122 | _success = function() {
123 | var stream = _microphone.media.localMediaStream;
124 | _microphone.audioInput = _microphone.context.createMediaStreamSource(stream);
125 | _microphone.recorder.onaudioprocess = function(e) {
126 | if (!_microphone.isReady) return;
127 | var inputbuffer = e.inputBuffer,
128 | channelCount = inputbuffer.numberOfChannels,
129 | length = inputbuffer.length;
130 | channel = new Float32Array(channelCount * length);
131 | for (var i = 0; i < length; i++) {
132 | for (var j = 0; j < channelCount; j++) {
133 | channel[i * channelCount + j] = inputbuffer.getChannelData(j)[i];
134 | }
135 | }
136 | _microphone.buffer.push(channel);
137 | _microphone.bufferLength += channel.length;
138 | };
139 | _microphone.startRecord();
140 | _microphone.updateSource(success);
141 | }
142 | this.media.start(_success, error);
143 | }
144 |
145 | this.startRecord = function() {
146 | this.isReady = true;
147 | var volume = this.context.createGain();
148 | this.audioInput.connect(volume);
149 | volume.connect(this.recorder);
150 | this.recorder.connect(this.context.destination);
151 | }
152 |
153 | this.stopRecord = function() {
154 | this.recorder.disconnect();
155 | }
156 |
157 | this.stop = function(url) {
158 | this.isReady = false;
159 | this.stopRecord();
160 | this.media.stop(url);
161 | }
162 |
163 | this.source = function() {
164 | return this.src;
165 | }
166 |
167 | this.init = function(_config) {
168 | _config = _config || {};
169 | this.isReady = false;
170 | this.media = new Media({
171 | audio: true
172 | });
173 | audioContext = window.AudioContext || window.webkitAudioContext;
174 | this.context = new audioContext();
175 | this.config = {
176 | inputSampleRate: this.context.sampleRate,
177 | //输入采样率,取决于平台
178 | inputSampleBits: 16,
179 | //输入采样数位 8, 16
180 | outputSampleRate: _config.sampleRate || (44100 / 6),
181 | //输出采样率
182 | oututSampleBits: _config.sampleBits || 8,
183 | //输出采样数位 8, 16
184 | channelCount: _config.channelCount || 2,
185 | //声道数
186 | cycle: _config.cycle || 500,
187 | //更新周期,单位ms
188 | volume: _config.volume || 1 //音量
189 | };
190 | var bufferSize = 4096;
191 | this.recorder = this.context.createScriptProcessor(bufferSize, this.config.channelCount, this.config.channelCount); // 第二个和第三个参数指的是输入和输出的声道数
192 | this.buffer = [];
193 | this.bufferLength = 0;
194 |
195 | return this;
196 | }
197 |
198 | this.compress = function() { //合并压缩
199 | //合并
200 | var buffer = this.buffer,
201 | bufferLength = this.bufferLength;
202 | this.buffer = []; //处理缓存并将之清空
203 | this.bufferLength = 0;
204 | var data = new Float32Array(bufferLength);
205 | for (var i = 0, offset = 0; i < buffer.length; i++) {
206 | data.set(buffer[i], offset);
207 | offset += buffer[i].length;
208 | }
209 | //压缩
210 | var config = this.config,
211 | compression = parseInt(config.inputSampleRate / config.outputSampleRate),
212 | //计算压缩率
213 | length = parseInt(data.length / compression),
214 | result = new Float32Array(length);
215 | index = 0;
216 | while (index < length) {
217 | result[index] = data[index++ * compression];
218 | }
219 | return result;
220 | }
221 |
222 | this.encodeWAV = function(bytes) {
223 | var config = this.config,
224 | sampleRate = Math.min(config.inputSampleRate, config.outputSampleRate),
225 | sampleBits = Math.min(config.inputSampleBits, config.oututSampleBits),
226 | dataLength = bytes.length * (sampleBits / 8),
227 | buffer = new ArrayBuffer(44 + dataLength),
228 | view = new DataView(buffer),
229 | channelCount = config.channelCount,
230 | offset = 0,
231 | volume = config.volume;
232 |
233 | writeUTFBytes = function(str) {
234 | for (var i = 0; i < str.length; i++) {
235 | view.setUint8(offset + i, str.charCodeAt(i));
236 | }
237 | };
238 | // 资源交换文件标识符
239 | writeUTFBytes('RIFF');
240 | offset += 4;
241 | // 下个地址开始到文件尾总字节数,即文件大小-8
242 | view.setUint32(offset, 44 + dataLength, true);
243 | offset += 4;
244 | // WAV文件标志
245 | writeUTFBytes('WAVE');
246 | offset += 4;
247 | // 波形格式标志
248 | writeUTFBytes('fmt ');
249 | offset += 4;
250 | // 过滤字节,一般为 0x10 = 16
251 | view.setUint32(offset, 16, true);
252 | offset += 4;
253 | // 格式类别 (PCM形式采样数据)
254 | view.setUint16(offset, 1, true);
255 | offset += 2;
256 | // 通道数
257 | view.setUint16(offset, channelCount, true);
258 | offset += 2;
259 | // 采样率,每秒样本数,表示每个通道的播放速度
260 | view.setUint32(offset, sampleRate, true);
261 | offset += 4;
262 | // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
263 | view.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true);
264 | offset += 4;
265 | // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
266 | view.setUint16(offset, channelCount * (sampleBits / 8), true);
267 | offset += 2;
268 | // 每样本数据位数
269 | view.setUint16(offset, sampleBits, true);
270 | offset += 2;
271 | // 数据标识符
272 | writeUTFBytes('data');
273 | offset += 4;
274 | // 采样数据总数,即数据总大小-44
275 | view.setUint32(offset, dataLength, true);
276 | offset += 4;
277 | // 写入采样数据
278 | if (sampleBits === 8) {
279 | for (var i = 0; i < bytes.length; i++, offset++) {
280 | var val = bytes[i] * (0x7FFF * volume);
281 | val = parseInt(255 / (65535 / (val + 32768)));
282 | view.setInt8(offset, val, true);
283 | }
284 | } else if (sampleBits === 16) {
285 | for (var i = 0; i < bytes.length; i++, offset += 2) {
286 | var val = bytes[i] * (0x7FFF * volume);
287 | view.setInt16(offset, val, true);
288 | }
289 | }
290 | return new Blob([view], {
291 | type: 'audio/wav'
292 | });
293 | }
294 |
295 | this.updateSource = function(callback) {
296 | if (!this.isReady) return;
297 | var _microphone = this,
298 | blob = this.encodeWAV(this.compress());
299 | url = this.src;
300 | url && window.URL && window.URL.revokeObjectURL && window.URL.revokeObjectURL(url);
301 | if (blob.size > 44) { //size为44的时候,数据部分为空
302 | this.CurrentData = blob;
303 | this.src = window.URL.createObjectURL(blob);
304 | }
305 | callback && callback();
306 | setTimeout(function() {
307 | _microphone.updateSource(callback);
308 | }, _microphone.config.cycle);
309 | }
310 |
311 | this.init();
312 | }
313 |
314 | window.Video = function(obj) {
315 |
316 | this.obj = $(obj);
317 |
318 | this.dom = this.obj.get(0);
319 |
320 | this.init = function(src) {
321 | this.canPlay = false;
322 | this.dom.src = this.src = src;
323 | return this;
324 | }
325 |
326 | this.play = function() {
327 | this.dom.play();
328 | return this;
329 | };
330 |
331 | this.pause = function() {
332 | this.dom.pause();
333 | return this;
334 | };
335 |
336 | this.CurrentFrame = function(width, height) {
337 | var _canvas = new myCanvas(),
338 | canvas = _canvas.dom,
339 | image = new Image(),
340 | ctx = canvas.getContext("2d");
341 | //重置canvans宽高
342 | canvas.width = width || this.dom.width;
343 | canvas.height = height || this.dom.height;
344 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
345 | image.src = canvas.toDataURL("image/png");
346 | _canvas.remove();
347 | _canvas = null;
348 | return image;
349 | };
350 |
351 | this.CurrentFrameData = function(width, height) {
352 | var _canvas = new myCanvas(),
353 | canvas = _canvas.dom,
354 | ctx = canvas.getContext("2d");
355 | //重置canvans宽高
356 | canvas.width = width || this.dom.width;
357 | canvas.height = height || this.dom.height;
358 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
359 | var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
360 | _canvas.remove();
361 | _canvas = null;
362 | return data;
363 | };
364 |
365 | this.CurrentBlob = function(width, height) {
366 | var _canvas = new myCanvas(),
367 | canvas = _canvas.dom,
368 | ctx = canvas.getContext("2d");
369 | //重置canvans宽高
370 | canvas.width = width || this.dom.width;
371 | canvas.height = height || this.dom.height;
372 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
373 | var data = dataURLtoBlob(canvas.toDataURL("image/png"))
374 | _canvas.remove();
375 | _canvas = null;
376 | return data;
377 | };
378 | }
379 |
380 | window.audio = function(obj) {
381 |
382 | this.obj = $(obj);
383 |
384 | this.dom = this.obj.get(0);
385 |
386 | this.init = function(src) {
387 | if (src) {
388 | this.dom.src = this.src = src;
389 | }
390 | return this;
391 | }
392 |
393 | this.play = function() {
394 | this.dom.play();
395 | return this;
396 | };
397 |
398 | this.pause = function() {
399 | this.dom.pause();
400 | this.source && this.source.stop(this.src);
401 | return this;
402 | };
403 | }
404 |
405 | window.dataURLtoBlob = function(dataurl) {
406 | var arr = dataurl.split(','),
407 | mime = arr[0].match(/:(.*?);/)[1],
408 | bstr = atob(arr[1]),
409 | n = bstr.length,
410 | u8arr = new Uint8Array(n);
411 | while (n--) {
412 | u8arr[n] = bstr.charCodeAt(n);
413 | }
414 | return new Blob([u8arr], {
415 | type: mime
416 | });;
417 | }
418 |
419 | window.readBlobAsDataURL = function(blob, callback) {
420 | var a = new FileReader();
421 | a.onload = function(e) {
422 | callback(e.target.result);
423 | };
424 | a.readAsDataURL(blob);
425 | }
426 |
427 | window.DataURLtoString = function(dataurl) {
428 | return window.atob(dataurl.substring("data:text/plain;base64,".length))
429 | }
430 |
431 | window.myCanvas = function(width, height, isDisplay) {
432 |
433 | (function(_this) {
434 | width = width || 640, height = height || 360;
435 | display = isDisplay ? '' : ' style="display: none;"';
436 | obj = $('
');
437 | obj.appendTo("body");
438 | _this.dom = obj.get(0);
439 | })(this)
440 |
441 | this.remove = function() {
442 | $(this.dom).remove();
443 | }
444 | }
445 | })(window);
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/microphoneTest1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
浏览器捕捉麦克风功能测试1
5 |
6 |
7 |
8 |
24 |
25 |
26 |
27 | 浏览器捕捉麦克风功能测试1
28 |
29 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/microphoneTest2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
浏览器捕捉麦克风功能测试2
13 |
14 |
15 |
16 | 浏览器捕捉麦克风功能测试2
17 |
18 |
19 |
20 |
21 |
22 | 点击“开始录制”按钮开始录音
23 |
24 |
25 |
213 |
214 |
215 |
216 |
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/Toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/Toolbar.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close_down.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/btn_close_hover.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/canvaspost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/canvaspost.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/headpic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/headpic.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/mod_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/mod_chat.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/mod_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/mod_file.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/mod_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/mod_video.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/mod_voice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/mod_voice.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/sprite_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/sprite_main.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/pic/websocket/videopost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/pic/websocket/videopost.png
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/server.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | // http服务设置
3 | var express = require('express'),
4 | app = express(),
5 | http = require('http').createServer(app);
6 |
7 | app.use(express.static(__dirname));
8 |
9 | http.listen(3000, function(){
10 | console.log('httpServer: listening on "http://localhost:3000"');
11 | });
12 |
13 | // websocket服务设置
14 | var WebSocketServer = require('ws').Server,
15 | wss = new WebSocketServer({
16 | port: 3002,
17 | // host: "localhost",
18 | path: "/websocket/chat"
19 | }, function() {
20 | console.log('websocketServer: listening on "ws://localhost:3002/chat"');
21 | }),
22 | connectionList = new ArrayList(),
23 | roomInfo = {};
24 |
25 | //连接建立
26 | wss.on('connection', function(ws) {
27 | var wsid = ws._ultron.id,
28 | name = "游客" + new Date().getTime().toString();
29 |
30 | console.log("%s 加入了聊天室.",name);
31 |
32 | roomInfo = connectionList.length > 0 ? roomInfo : {
33 | creater: name,
34 | createTime: new Date().toLocaleString()
35 | };
36 |
37 | connectionList.add({
38 | wsid: wsid,
39 | name: name,
40 | connect: ws
41 | });
42 |
43 | var dests = getDests(),
44 | setNameMsg = {
45 | host: name,
46 | type: 6, //setName
47 | roomInfo: roomInfo,
48 | dests: dests
49 | },
50 | joinMsg = {
51 | host: name,
52 | type: 1, //setName
53 | roomInfo: roomInfo,
54 | dests: dests
55 | },
56 | msg = JSON.stringify(setNameMsg);
57 |
58 | //设置名称
59 | ws.send(msg);
60 |
61 | msg = JSON.stringify(joinMsg);
62 |
63 | //通知所有人有新连接
64 | connectionList.foreach(function(obj) {
65 | obj.connect.send(msg);
66 | });
67 |
68 | //收到消息
69 | ws.on('message', function(message) {
70 | console.log('收到%s的消息:%s', name, message);
71 |
72 | //反序列化
73 | var msg = JSON.parse(message);
74 |
75 | msg.host = name;
76 |
77 | //序列化
78 | message = JSON.stringify(msg);
79 |
80 | connectionList.foreach(function(obj) {
81 | obj.connect.send(message);
82 | });
83 | });
84 |
85 | //连接断开
86 | ws.on('close', function(message) {
87 | console.log("%s 离开了聊天室.", name );
88 |
89 | //移除当前连接
90 | var index = connectionList.find(function(obj) {
91 | return obj.wsid == wsid;
92 | });
93 |
94 | index > -1 && connectionList.removeAt(index);
95 |
96 | var closeMsg = {
97 | host: name,
98 | type: 2, //close
99 | dests: getDests()
100 | };
101 |
102 | message = JSON.stringify(closeMsg);
103 |
104 | connectionList.foreach(function(obj) {
105 | obj.connect.send(message);
106 | });
107 | });
108 | });
109 |
110 | function getDests() {
111 | var dests = [];
112 |
113 | connectionList.foreach(function(obj) {
114 | dests[dests.length] = obj.name;
115 | });
116 |
117 | return dests;
118 | }
119 |
120 | function ArrayList(array) {
121 | this.array = typeof array !== 'undefined' && array instanceof Array ? array : new Array();
122 | this.length = this.array.length;
123 | var that = this,
124 | setLength = function() {
125 | that.length = that.array.length;
126 | };
127 |
128 | this.get = function(index){
129 | return this.array[index];
130 | }
131 | this.add = function(obj) {
132 | this.array.push(obj);
133 | setLength();
134 | };
135 |
136 | this.indexOf = function(obj) {
137 | return this.array.indexOf(obj);
138 | }
139 |
140 | this.find = function(callback) {
141 | for(var i = 0; i < this.length; i++){
142 | if(callback(this.get(i))) {
143 | return i;
144 | }
145 | }
146 | return -1;
147 | }
148 |
149 | this.removeAt = function(index){
150 | this.array.splice(index, 1);
151 | setLength();
152 | };
153 |
154 | this.remove = function(obj){
155 | var index = this.indexOf(obj);
156 | if (index >= 0){
157 | this.removeAt(index);
158 | }
159 | };
160 |
161 | this.clear = function(){
162 | this.array.length = 0;
163 | setLength();
164 | };
165 |
166 | this.insertAt = function(index, obj){
167 | this.array.splice(index, 0, obj);
168 | setLength();
169 | };
170 |
171 | this.foreach = function(callback) {
172 | for(var i = 0; i < this.length; i++){
173 | callback(this.get(i));
174 | }
175 | };
176 | }
177 | })();
178 |
--------------------------------------------------------------------------------
/websocket-samples/Nodejs-Websocket/启动服务.bat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Nodejs-Websocket/启动服务.bat
--------------------------------------------------------------------------------
/websocket-samples/README.md:
--------------------------------------------------------------------------------
1 | # 这是基于websocket的在线聊天室demo
2 |
3 | 其中Tomcat-Websocket项目是基于tomcat服务器的java版服务端实现,Nodejs-Websocket项目是基于Nodejs的js版服务端实现。两个项目的服务端逻辑基本一致,客户端代码可以通用。此外,前者所有功能都基于websocket实现,后者采用webrtc技术实现语音视频的通讯,相比之下技术更成熟,性能更好。
4 |
5 | ## 主要功能
6 | 浏览器端文本、视频、语音的即时通讯。
7 |
8 | ## 文档
9 | 参考简书:[https://www.jianshu.com/nb/4071127](https://www.jianshu.com/nb/4071127)
10 |
11 | ## Docker支持
12 | 已添加docker-compose.yml和Dockerfile,可以很方便的在docker下运行demo。
13 |
14 | 1. 获取项目
15 | ```bash
16 | git clone git://github.com/anyesu/websocket
17 | ```
18 |
19 | 2. 使用 `docker-compose` 启动容器
20 | ```bash
21 | cd websocket/websocket-samples && docker-compose up
22 | ```
23 |
24 | 3. 访问 `yourip:8080` 或者 `yourip:3000`
25 |
26 | ## 维护说明
27 | 本目录下demo后续不再维护,仅作为博客配套的例子。tomcat版已重构为maven项目websocket-chat,以后针对这个项目进行维护。
28 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 | MAINTAINER anyesu
3 |
4 | # 拷贝项目
5 | COPY . /usr/anyesu/tmp/Tomcat-Websocket
6 |
7 | RUN echo -e "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main\n\
8 | https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/community" > /etc/apk/repositories && \
9 | # 设置时区
10 | apk --update add ca-certificates && \
11 | apk add tzdata && \
12 | ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
13 | echo "Asia/Shanghai" > /etc/timezone && \
14 | # 安装jdk
15 | apk add openjdk7 && \
16 | # 安装wget
17 | apk add wget && \
18 | tmp=/usr/anyesu/tmp && \
19 | cd /usr/anyesu && \
20 | # 下载tomcat
21 | tomcatVer=7.0.82 && \
22 | wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v$tomcatVer/bin/apache-tomcat-$tomcatVer.tar.gz && \
23 | tar -zxvf apache-tomcat-$tomcatVer.tar.gz && \
24 | mv apache-tomcat-$tomcatVer tomcat && \
25 | # 清空webapps下自带项目
26 | rm -r tomcat/webapps/* && \
27 | rm apache-tomcat-$tomcatVer.tar.gz && \
28 | cd $tmp && \
29 | # 编译源码
30 | proj=$tmp/Tomcat-Websocket && \
31 | src=$proj/src && \
32 | tomcatBase=/usr/anyesu/tomcat && \
33 | classpath="$tomcatBase/lib/servlet-api.jar:$tomcatBase/lib/websocket-api.jar:$proj/WebRoot/WEB-INF/lib/fastjson-1.1.41.jar" && \
34 | output=$proj/WebRoot/WEB-INF/classes && \
35 | mkdir -p $output && \
36 | /usr/lib/jvm/java-1.7-openjdk/bin/javac -sourcepath $src -classpath $classpath -d $output `find $src -name "*.java"` && \
37 | # 拷贝到tomcat
38 | mv $proj/WebRoot $tomcatBase/webapps/ROOT && \
39 | rm -rf $tmp && \
40 | apk del wget && \
41 | # 清除apk缓存
42 | rm -rf /var/cache/apk/* && \
43 | # 添加普通用户
44 | addgroup -S group_docker && adduser -S -G group_docker user_docker && \
45 | # 修改目录所有者
46 | chown user_docker:group_docker -R /usr/anyesu
47 |
48 | # 设置环境变量
49 | ENV JAVA_HOME /usr/lib/jvm/java-1.7-openjdk
50 | ENV CATALINA_HOME /usr/anyesu/tomcat
51 | ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin
52 |
53 | # 暴露端口
54 | EXPOSE 8080
55 |
56 | # 启动命令(前台程序)
57 | CMD ["catalina.sh", "run"]
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Class-Path:
3 |
4 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/WEB-INF/lib/fastjson-1.1.41.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/WEB-INF/lib/fastjson-1.1.41.jar
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tomcat-Websocket
4 |
5 | index.html
6 | index.htm
7 | index.jsp
8 | default.html
9 | default.htm
10 | default.jsp
11 |
12 |
13 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/cameraTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
浏览器打开摄像头功能测试
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
59 |
60 |
61 |
62 | 一个WebRTC插件
63 |
64 |
65 |
66 |
67 |
68 |
71 |
72 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/css/chat.css:
--------------------------------------------------------------------------------
1 | /*chrome浏览器下滚动条样式*/
2 | ::-webkit-scrollbar-track-piece{
3 | background-color:#fff;
4 | -webkit-border-radius:0;
5 | }
6 | ::-webkit-scrollbar{
7 | width:10px;
8 | height:10px
9 | }
10 | ::-webkit-scrollbar-thumb{
11 | background-color:rgba(0,0,0,0.2);
12 | border-radius:5px;
13 | }
14 | ::-webkit-scrollbar-thumb:hover{
15 | background-color:rgba(0,0,0,0.3);
16 | }
17 | ::-webkit-scrollbar-arrow {
18 | color:#F00;
19 | backgound:#0F0;
20 | }
21 | /*IE下滚动条样式*/
22 | * {
23 | scrollbar-face-color: gray;
24 | scrollbar-arrow-color: #F00;
25 | scrollbar-track-color: transparent;
26 | }
27 |
28 |
29 | *{margin:0; padding:0; cursor:default;}
30 | html, body{width:100%; height:100%; font-family:微软雅黑; position:absolute; font-size:12px; overflow-x:auto; overflow-y:auto; text-align:center;}
31 | div{width:100%;overflow:hidden;}
32 | h2{color: red;}
33 | .mwd{margin: 0 auto;text-align: left;position: relative;top: 0%;left: 0%;width:720px;min-width:720px;height:580px;min-height:580px;overflow:hidden;box-shadow: 5px 5px 5px rgba(0,0,0,0.4);-webkit-box-shadow: 5px 5px 5px rgba(0,0,0,0.4);border: 1px solid #d4dce0;border-radius: 7px;background-color: #eaf1f6;}
34 | .mwd_full{width:100%;height:100%;}
35 | .pageTop{width: 100%;height: 85px;float: left;border-bottom: 1px solid #d4dce0;position: relative;left: 0;top: 0;}
36 | .pageTop .title{position: absolute;top: 5px;left: 10px;width: 500px;height: 45px;}
37 | .pageTop .title .host{color:red;}
38 | .pageTop .toolbar{position: absolute;right: 0px;top: -2px;overflow: hidden;width: auto;height: auto;}
39 | .pageTop .toolbar a{cursor: pointer;float: left;width: 19px;height: 21px;margin-left: 5px;line-height: 10;overflow: hidden;background: url(../pic/websocket/sprite_main.png) no-repeat;display: block;}
40 | .pageTop .toolbar .close{background-position: -64px -59px;}
41 | .pageTop .toolbar .close:hover{background-position: -64px -30px;}
42 | .pageTop .toolbar .close:active{background-position: -64px -2px;}
43 | .pageTop .toolbar .min{background-position: -7px -59px;}
44 | .pageTop .toolbar .min:hover{background-position: -7px -30px;}
45 | .pageTop .toolbar .min:active{background-position: -7px -2px;}
46 | .pageTop .toolbar .max{background-position: -36px -59px;}
47 | .pageTop .toolbar .max:hover{background-position: -36px -30px;}
48 | .pageTop .toolbar .max:active{background-position: -36px -2px;}
49 | .pageTop .func{cursor: Default;position: absolute;bottom: 3px;left: 7px;height: 23px;max-width: 80%;text-align: left;overflow: visible;}
50 | .pageTop .func a{cursor: pointer;padding: 10px 5px 9px 34px;font-size: 14px;}
51 | .pageTop .func a:hover {cursor: pointer;border: 1px solid #666;border-bottom: 0;border-radius: 3px;padding: 10px 4px 9px 33px;background-position: 4px 9px;}
52 | .pageTop .func a.click {cursor: Default;border: 1px solid #666;border-bottom: 0;border-radius: 3px;padding: 10px 4px 9px 33px;background-position: 4px 9px;}
53 | .pageTop .func a.check {cursor: Default;border: 1px solid #666;border-bottom: 0;border-radius: 3px;padding: 10px 4px 9px 33px;background-position: 4px 9px;}
54 | .pageTop .func a.chat{background: url(../pic/websocket/mod_chat.png) no-repeat 5px 9px;}
55 | .pageTop .func a.voice{background: url(../pic/websocket/mod_voice.png) no-repeat 5px;}
56 | .pageTop .func a.video{background: url(../pic/websocket/mod_video.png) no-repeat 5px;}
57 | .pageTop .func a.file{background: url(../pic/websocket/mod_file.png) no-repeat 5px;}
58 | .mwd .mode-text .pageLeft{width: 519px;height: 100%;}
59 | .mwd .mode-text .pageLeft .edit{width: 100%;height: 135px;border-top: 1px solid #d4dce0;}
60 | .mwd .mode-text .pageLeft .edit .buttons{height:30px;}
61 | .mwd .mode-text .pageLeft .edit .buttons .button{text-align: center;line-height: 20px;font-weight: bold;width: 40px;height: 20px;margin: 2px 3px;padding: 2px 10px;border-radius: 3px;background-color: #388bff;border-color: #388bff;float: right;}
62 | .mwd .mode-text .pageLeft .edit .buttons .button:hover{cursor:pointer;box-shadow: 1px 1px 1px rgba(0,0,0,0.4);}
63 | .mwd .mode-text .pageLeft .edit .buttons .info{float: left;font-size: 8px;padding: 5px 0;margin: 0 0 0 10px;height: 20px;max-width: 260px;vertical-align: middle;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
64 | .mwd .mode-text .pageLeft .edit .editTool{background: url(../pic/websocket/Toolbar.png) no-repeat 5px;width: 100%;height: 25px;}
65 | .mwd .mode-text .pageLeft .edit .mainedit{box-sizing: border-box;padding: 5px;font-size: 16px;font-weight: 800;display:block;resize: none;width: 100%;height: 80px;text-align: left;border-color: rgb(228, 186, 20);outline: 0;}
66 | .mwd .mode-text .pageLeft .content{padding: 10px 0;width: 100%;height: 339px;overflow-x: hidden;overflow-y: auto;}
67 | .mwd .mode-text .pageLeft .content .contentadd{overflow:visible;width:100%;}
68 | .mwd .mode-text .pageLeft .content .row{overflow: hidden; display: block;position: relative;}
69 | .mwd .mode-text .pageLeft .content .row i{transform:rotate(-45deg);-ms-transform:rotate(-45deg); /* Internet Explorer */-moz-transform:rotate(-45deg); /* Firefox */-webkit-transform:rotate(-45deg); /* Safari 和 Chrome */-o-transform:rotate(-45deg); /* Opera */border-left: 5px solid transparent;border-right: 5px solid transparent;border-bottom: 5px solid rgb(228, 186, 20);display: block;width: 1px;height: 1px;overflow: hidden;position: absolute;top: 8px;right: 44px;}
70 | .mwd .mode-text .pageLeft .content .row i.src{transform:rotate(45deg);-ms-transform:rotate(45deg); /* Internet Explorer */-moz-transform:rotate(45deg); /* Firefox */-webkit-transform:rotate(45deg); /* Safari 和 Chrome */-o-transform:rotate(45deg); /* Opera */left: 44px;}
71 | .mwd .mode-text .pageLeft .content .row .headpic{background: url(../pic/websocket/headpic.png);background-size: 100%;position: absolute;background-color: #fff;height: 30px;width: 30px;border-radius: 4px;right: 10px;}
72 | .mwd .mode-text .pageLeft .content .row span.headpic.src{left: 10px;}
73 | .mwd .mode-text .pageLeft .content dl{background-color: rgb(244,230,219);margin:5px 20px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;}
74 | .mwd .mode-text .pageLeft .content div.src{font-size: 16px;width: auto;background-color: rgb(244,230,219);margin:5px 50px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;float: left;}
75 | .mwd .mode-text .pageLeft .content div.dest{font-size: 16px;width: auto;background-color: rgb(244,230,219);margin:5px 50px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;float: right;}
76 | .mwd .mode-text .pageLeft .content dl .blue{color: green;}
77 | .mwd .mode-text .pageLeft .content div .time{font-size: 8px;padding: 0;margin: 0;width: 90px;}
78 |
79 | .mwd .mode-text .pageRight{background-color: #eaf1f6;overflow-x: hidden;overflow-y: auto;padding:20px;width: 160px;border-left: 1px solid #d4dce0;height: 100%;right: 0;top: 86px;position: absolute;}
80 | .mwd .mode-text .pageRight .row{margin: 5px 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
81 | .mwd .mode-text .pageRight .row .user{margin-left: 5px;line-height: 40px;width:160px;font-size: 20px;}
82 | .mwd .mode-text .pageRight .row .headpic{width: 40px;height: 40px;float: left;background-color: #fff;}
83 |
84 |
85 | .mwd .mode-video .pageLeft{width: 420px;height: 100%;}
86 | .mwd .mode-video .pageLeft .videocontent{padding: 10px 0;width: 100%;height: 339px;overflow-x: hidden;overflow-y: auto;}
87 | .mwd .mode-video .pageLeft .videocontent canvas{margin: 10px;border-radius: 5px;border: 3px solid #388bff;}
88 | .mwd .mode-video .pageLeft .row{margin: 5px 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
89 | .mwd .mode-video .pageLeft .row .user{margin-left: 5px;line-height: 40px;width:160px;font-size: 20px;}
90 | .mwd .mode-video .pageLeft .row .headpic{width: 40px;height: 40px;float: left;background-color: #fff;}
91 | .mwd .mode-video .pageLeft .myView{text-align: center;}
92 | .mwd .mode-video .pageLeft .myView video#myVideo{margin: 10px;width: auto;border-radius: 5px;border: 3px solid rgb(228, 186, 20);}
93 |
94 | .mwd .mode-video .pageRight{background-color: #eaf1f6;overflow-x: hidden;overflow-y: auto;width: 300px;border-left: 1px solid #d4dce0;height: 100%;right: 0;top: 86px;position: absolute;}
95 | .mwd .mode-video .pageRight .edit{width: 100%;height: 135px;border-top: 1px solid #d4dce0;}
96 | .mwd .mode-video .pageRight .edit .buttons{height:30px;}
97 | .mwd .mode-video .pageRight .edit .buttons .button{text-align: center;line-height: 20px;font-weight: bold;width: 40px;height: 20px;margin: 2px 3px;padding: 2px 10px;border-radius: 3px;background-color: #388bff;border-color: #388bff;float: right;}
98 | .mwd .mode-video .pageRight .edit .buttons .button:hover{cursor:pointer;box-shadow: 1px 1px 1px rgba(0,0,0,0.4);}
99 | .mwd .mode-video .pageRight .edit .buttons .info{float: left;font-size: 8px;padding: 5px 0;margin: 0 0 0 10px;height: 20px;max-width: 150px;vertical-align: middle;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
100 | .mwd .mode-video .pageRight .edit .editTool{background: url(../pic/websocket/Toolbar.png) no-repeat 5px;width: 100%;height: 25px;}
101 | .mwd .mode-video .pageRight .edit .mainedit{box-sizing: border-box;padding: 5px;font-size: 16px;font-weight: 800;display:block;resize: none;width: 100%;height: 80px;text-align: left;border-color: rgb(228, 186, 20);outline: 0;}
102 | .mwd .mode-video .pageRight .content{padding: 10px 0;width: 100%;height: 339px;overflow-x: hidden;overflow-y: auto;}
103 | .mwd .mode-video .pageRight .content .contentadd{overflow:visible;width:100%;}
104 | .mwd .mode-video .pageRight .content .row{overflow: hidden; display: block;position: relative;}
105 | .mwd .mode-video .pageRight .content .row i{transform:rotate(-45deg);-ms-transform:rotate(-45deg); /* Internet Explorer */-moz-transform:rotate(-45deg); /* Firefox */-webkit-transform:rotate(-45deg); /* Safari 和 Chrome */-o-transform:rotate(-45deg); /* Opera */border-left: 5px solid transparent;border-right: 5px solid transparent;border-bottom: 5px solid rgb(228, 186, 20);display: block;width: 1px;height: 1px;overflow: hidden;position: absolute;top: 8px;right: 44px;}
106 | .mwd .mode-video .pageRight .content .row i.src{transform:rotate(45deg);-ms-transform:rotate(45deg); /* Internet Explorer */-moz-transform:rotate(45deg); /* Firefox */-webkit-transform:rotate(45deg); /* Safari 和 Chrome */-o-transform:rotate(45deg); /* Opera */left: 44px;}
107 | .mwd .mode-video .pageRight .content .row .headpic{background: url(../pic/websocket/headpic.png);background-size: 100%;position: absolute;background-color: #fff;height: 30px;width: 30px;border-radius: 4px;right: 10px;}
108 | .mwd .mode-video .pageRight .content .row span.headpic.src{left: 10px;}
109 | .mwd .mode-video .pageRight .content dl{background-color: rgb(244,230,219);margin:5px 20px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;}
110 | .mwd .mode-video .pageRight .content div.src{font-size: 16px;width: auto;background-color: rgb(244,230,219);margin:5px 50px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;float: left;}
111 | .mwd .mode-video .pageRight .content div.dest{font-size: 16px;width: auto;background-color: rgb(244,230,219);margin:5px 50px;border:3px solid rgb(228, 186, 20);border-radius: 7px;padding: 10px;max-width: 66%;float: right;}
112 | .mwd .mode-video .pageRight .content dl .blue{color: green;}
113 | .mwd .mode-video .pageRight .content div .time{font-size: 8px;padding: 0;margin: 0;width: 90px;}
114 |
115 | .audiocontent{display: none;}
116 | .error{color: #ee504c;}
117 |
118 |
119 | #min-max{position: absolute;right: 0px;bottom: 2px;left: 2px;overflow: hidden;height: auto;background-color: #660;width: 80px;}
120 | #min-max a{cursor: pointer;float: left;width: 19px;height: 21px;margin-left: 5px;line-height: 10;overflow: hidden;background: url(../pic/websocket/sprite_main.png) no-repeat;display: block;}
121 | #min-max .close{background-position: -64px -59px;}
122 | #min-max .close:hover{background-position: -64px -30px;}
123 | #min-max .close:active{background-position: -64px -2px;}
124 | #min-max .back{background-position: -7px -59px;}
125 | #min-max .back:hover{background-position: -7px -30px;}
126 | #min-max .back:active{background-position: -7px -2px;}
127 | #min-max .max{background-position: -36px -59px;}
128 | #min-max .max:hover{background-position: -36px -30px;}
129 | #min-max .max:active{background-position: -36px -2px;}
130 | .red{color: #ee504c}
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/js/WSClient.js:
--------------------------------------------------------------------------------
1 | (function(window) {
2 | Blob.prototype.appendAtFirst = function(blob) {
3 | return new Blob([blob, this])
4 | };
5 | window.WSClient = function(option) {
6 | var isReady = false,
7 | WS_Open = 1,
8 | WS_Close = 2,
9 | WS_MsgToAll = 3,
10 | WS_MsgToPoints = 4,
11 | WS_RequireLogin = 5,
12 | WS_setName = 6,
13 | types = ["文本", "视频", "语音"],
14 | getWebSocket = function(host) {
15 | var socket;
16 | if ('WebSocket' in window) {
17 | socket = new WebSocket(host)
18 | } else if ('MozWebSocket' in window) {
19 | socket = new MozWebSocket(host)
20 | }
21 | return socket
22 | },
23 | init = function(client, option) {
24 | client.socket = null;
25 | client.online = false;
26 | client.isUserClose = false;
27 | client.option = option || {};
28 | client.autoReconnect = client.option.autoReconnect || true
29 | };
30 | this.connect = function(host) {
31 | host = host || this.option.host;
32 | var client = this,
33 | option = client.option,
34 | socket = getWebSocket(host);
35 | if (socket == null) {
36 | console.log('错误: 当前浏览器不支持WebSocket,请更换其他浏览器', true);
37 | alert('错误: 当前浏览器不支持WebSocket,请更换其他浏览器');
38 | return
39 | }
40 | socket.onopen = function() {
41 | var onopen = option.onopen,
42 | type = types[option.type];
43 | console.log('WebSocket已连接.');
44 | console.log("%c类型:" + type, "color:rgb(228, 186, 20)");
45 | onopen && onopen()
46 | };
47 | socket.onclose = function() {
48 | var onclose = option.onclose,
49 | type = types[option.type];
50 | client.online = false;
51 | console.error('WebSocket已断开.');
52 | console.error("%c类型:" + type, "color:rgb(228, 186, 20)");
53 | onclose && onclose();
54 | if (!client.isUserClose && option.autoReconnect) {
55 | client.initialize()
56 | }
57 | };
58 | socket.onmessage = function(message) {
59 | if (typeof(message.data) == "string") {
60 | var msg = JSON.parse(message.data);
61 | switch (msg.type) {
62 | case WS_Open:
63 | option.wsonopen && option.wsonopen(msg);
64 | break;
65 | case WS_Close:
66 | option.wsonclose && option.wsonclose(msg);
67 | break;
68 | case WS_MsgToAll:
69 | case WS_MsgToPoints:
70 | option.wsonmessage && option.wsonmessage(msg);
71 | break;
72 | case WS_RequireLogin:
73 | option.wsrequirelogin && option.wsrequirelogin();
74 | break;
75 | case WS_setName:
76 | option.userName = msg.host;
77 | option.wssetname && option.wssetname(msg);
78 | break
79 | }
80 | } else if (message.data instanceof Blob) {
81 | option.wsonblob && option.wsonblob(message)
82 | }
83 | };
84 | isReady = true;
85 | this.socket = socket;
86 | return this
87 | };
88 | this.initialize = function(param) {
89 | return this.connect(this.option.host + (param ? "?" + param : ""))
90 | };
91 | this.sendString = function(message) {
92 | return isReady && this.socket.send(message)
93 | };
94 | this.sendBlob = function(blob) {
95 | blob = blob.appendAtFirst(this.option.userName);
96 | var result = isReady && this.socket.send(blob);
97 | blob = null;
98 | return result;
99 | };
100 | this.close = function() {
101 | this.isReady = false;
102 | this.online = false;
103 | this.isUserClose = true;
104 | this.socket.close();
105 | this.socket = null;
106 | return true
107 | };
108 | this.isMe = function(name) {
109 | return this.option.userName == name
110 | }
111 | init(this, option)
112 | }
113 | })(window);
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/js/app.js:
--------------------------------------------------------------------------------
1 | $(document.body).ready(function(e) {
2 | $(videoClient.canvas.dom).appendTo("body");
3 | NO_SOURCE.src = "/ws/pic/websocket/canvaspost.png";
4 | textClient.initialize();
5 | Console.setMode(Console.ChatMode);
6 | $(".pageTop .func a").on("click", function() {
7 | if ($(this).index() < 2) {
8 | if ($(this).attr("class").indexOf("click") > -1) return;
9 | $(".pageTop .func a.click").removeClass("click");
10 | $(this).addClass("click");
11 | Console.setMode($(this).index())
12 | }
13 | });
14 | $(".pageTop .func a.voice").on("click", function() {
15 | if ($(this).attr("class").indexOf("check") > -1) {
16 | $(this).removeClass("check");
17 | audioClient.close()
18 | } else {
19 | if (!audioClient.online) {
20 | Console.microphone = new MicroPhone(), Console.mySound = new audio("#mySound");
21 | audioClient.initialize("uid=" + audioClient.option.userName)
22 | }
23 | $(this).addClass("check")
24 | }
25 | });
26 | $(".mwd>:not(.pageLeft)").each(function() {
27 | this.onselectstart = function() {
28 | return false
29 | }
30 | });
31 | var ScrollConfig = {
32 | cursorcolor: "#ffdb51",
33 | cursoropacitymax: 0.5,
34 | cursorwidth: "5PX",
35 | cursorborder: "0px solid #000",
36 | grabcursorenabled: false,
37 | preservenativescrolling: false,
38 | nativeparentscrolling: true,
39 | enablescrollonselection: true
40 | };
41 | $("[MyScroll]").each(function() {
42 | $(this).niceScroll(ScrollConfig)
43 | });
44 | $(window).resize(function() {
45 | if (Console.fullScreen) Console.resize()
46 | });
47 | $(".pageTop .toolbar .min").bind("click", Console.toggleMin);
48 | $(".pageTop .toolbar .max").bind("click", Console.toggleFullScreen);
49 | $(".pageTop .toolbar .close").bind("click", Console.close);
50 | $("#min-max .back").bind("click", Console.toggleMin);
51 | $("#min-max .max").bind("click", Console.minToMax);
52 | $("#min-max .close").bind("click", Console.close);
53 | $(".mwd .pageLeft .edit .buttons .close").bind("click", Console.close)
54 | });
55 |
56 | function CloseWindow() {
57 | if (typeof(WeixinJSBridge) != "undefined") {
58 | WeixinJSBridge.call('closeWindow')
59 | } else {
60 | var opened = window.open('about:blank', '_self');
61 | opened.opener = null;
62 | opened.close()
63 | }
64 | }
65 | function StringBuffer(str) {
66 | this.strArray = new Array();
67 | this.strArray.push(str);
68 | this.append = function(appendStr) {
69 | this.strArray.push(appendStr)
70 | };
71 | this.toString = function() {
72 | return this.strArray.join("")
73 | }
74 | }
75 | $.fn.ctrlEnter = function(btns, fn) {
76 | var thiz = $(this);
77 | btns = $(btns);
78 |
79 | function performAction(e) {
80 | fn.call(thiz, e)
81 | };
82 | thiz.unbind();
83 | thiz.bind("keydown", function(e) {
84 | if (e.keyCode === 13 && e.ctrlKey) {
85 | thiz.val(thiz.val() + "\n");
86 | scrollToBottom(thiz);
87 | e.preventDefault()
88 | } else if (e.keyCode === 13) {
89 | performAction(e);
90 | e.preventDefault()
91 | }
92 | });
93 | btns.bind("click", performAction)
94 | }
95 | function htmlEncode(value) {
96 | return $('
').text(value).html()
97 | }
98 | function htmlDecode(value) {
99 | return $('
').html(value).text()
100 | }
101 | function scrollToBottom(obj) {
102 | obj[0].scrollTop = obj[0].scrollHeight
103 | }
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/js/media.js:
--------------------------------------------------------------------------------
1 | (function(window) {
2 | window.URL = window.URL || window.webkitURL || window.msURL || window.oURL;
3 |
4 | window.Media = function(option) {
5 |
6 | var type = 0; // 0.无,1.音频,2.视频,3.音视频
7 | if (option.video && option.audio) {
8 | type = 3;
9 | } else if (option.video) {
10 | type = 2;
11 | } else if (option.audio) {
12 | type = 1;
13 | }
14 |
15 | jAlert = function(msg, title, callback) {
16 | alert(msg);
17 | callback && callback();
18 | };
19 | sucCallBack = function(media, stream) {
20 | media.localMediaStream = stream;
21 | };
22 |
23 | errCallBack = function(error) {
24 | if (error.PERMISSION_DENIED) {
25 | jAlert('您拒绝了浏览器请求媒体的权限', '提示');
26 | } else if (error.NOT_SUPPORTED_ERROR) {
27 | jAlert('对不起,您的浏览器不支持摄像头/麦克风的API,请使用其他浏览器', '提示');
28 | } else if (error.MANDATORY_UNSATISFIED_ERROR) {
29 | jAlert('指定的媒体类型未接收到媒体流', '提示');
30 | } else {
31 | jAlert('相关硬件正在被其他程序使用中', '提示');
32 | }
33 | };
34 |
35 | this.source = function() {
36 | if (type < 2) {
37 | return null;
38 | }
39 | var stream = this.localMediaStream;
40 | return (window.URL && window.URL.createObjectURL) ? window.URL.createObjectURL(stream) : stream;
41 | }
42 |
43 | this.start = function(success, error) {
44 | var _media = this;
45 | if (this.localMediaStream.readyState) {
46 | success && success();
47 | return;
48 | }
49 | navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
50 | var userAgent = navigator.userAgent,
51 | msgTitle = '提示',
52 | notSupport = '对不起,您的浏览器不支持摄像头/麦克风的API,请使用其他浏览器',
53 | message = "为了获得更准确的测试结果,请尽量将面部置于红框中,然后进行拍摄、扫描。 点击“OK”后,请在屏幕上方出现的提示框选择“允许”,以开启摄像功能";
54 | try {
55 | if (navigator.getUserMedia) {
56 | if (userAgent.indexOf('MQQBrowser') > -1) {
57 | errCallBack({
58 | NOT_SUPPORTED_ERROR: 1
59 | });
60 | return false;
61 | }
62 | navigator.getUserMedia(option, function(stream) {
63 | sucCallBack(_media, stream);
64 | success && success();
65 | }, function(err) {
66 | errCallBack(err);
67 | error && error();
68 | });
69 | } else {
70 | /*
71 | if (userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Oupeng") == -1 && userAgent.indexOf("360 Aphone") == -1) {
72 |
73 | } //判断是否Safari浏览器
74 | */
75 | errCallBack({
76 | NOT_SUPPORTED_ERROR: 1
77 | });
78 | return false;
79 | }
80 | } catch (err) {
81 | errCallBack({
82 | NOT_SUPPORTED_ERROR: 1
83 | });
84 | return false;
85 | }
86 | return true;
87 | }
88 |
89 | this.stop = function(url) {
90 | url && window.URL && window.URL.revokeObjectURL && window.URL.revokeObjectURL(url);
91 | this.localMediaStream.readyState && this.localMediaStream.stop();
92 | this.localMediaStream = new Object();
93 | return true;
94 | };
95 |
96 | this.localMediaStream = new Object();
97 | }
98 |
99 | window.Camera = function() {
100 |
101 | this.start = function(success, error) {
102 | return this.media.start(success, error);
103 | };
104 |
105 | this.stop = function(url) {
106 | return this.media.stop(url);
107 | };
108 |
109 | this.source = function() {
110 | return this.media.source();
111 | };
112 |
113 | this.media = new Media({
114 | video: true
115 | });
116 | }
117 |
118 | window.MicroPhone = function() {
119 |
120 | this.start = function(success, error) {
121 | var _microphone = this;
122 | _success = function() {
123 | var stream = _microphone.media.localMediaStream;
124 | _microphone.audioInput = _microphone.context.createMediaStreamSource(stream);
125 | _microphone.recorder.onaudioprocess = function(e) {
126 | if (!_microphone.isReady) return;
127 | var inputbuffer = e.inputBuffer,
128 | channelCount = inputbuffer.numberOfChannels,
129 | length = inputbuffer.length;
130 | channel = new Float32Array(channelCount * length);
131 | for (var i = 0; i < length; i++) {
132 | for (var j = 0; j < channelCount; j++) {
133 | channel[i * channelCount + j] = inputbuffer.getChannelData(j)[i];
134 | }
135 | }
136 | _microphone.buffer.push(channel);
137 | _microphone.bufferLength += channel.length;
138 | };
139 | _microphone.startRecord();
140 | _microphone.updateSource(success);
141 | }
142 | this.media.start(_success, error);
143 | }
144 |
145 | this.startRecord = function() {
146 | this.isReady = true;
147 | var volume = this.context.createGain();
148 | this.audioInput.connect(volume);
149 | volume.connect(this.recorder);
150 | this.recorder.connect(this.context.destination);
151 | }
152 |
153 | this.stopRecord = function() {
154 | this.recorder.disconnect();
155 | }
156 |
157 | this.stop = function(url) {
158 | this.isReady = false;
159 | this.stopRecord();
160 | this.media.stop(url);
161 | }
162 |
163 | this.source = function() {
164 | return this.src;
165 | }
166 |
167 | this.init = function(_config) {
168 | _config = _config || {};
169 | this.isReady = false;
170 | this.media = new Media({
171 | audio: true
172 | });
173 | audioContext = window.AudioContext || window.webkitAudioContext;
174 | this.context = new audioContext();
175 | this.config = {
176 | inputSampleRate: this.context.sampleRate,
177 | //输入采样率,取决于平台
178 | inputSampleBits: 16,
179 | //输入采样数位 8, 16
180 | outputSampleRate: _config.sampleRate || (44100 / 6),
181 | //输出采样率
182 | oututSampleBits: _config.sampleBits || 8,
183 | //输出采样数位 8, 16
184 | channelCount: _config.channelCount || 2,
185 | //声道数
186 | cycle: _config.cycle || 500,
187 | //更新周期,单位ms
188 | volume: _config.volume || 1 //音量
189 | };
190 | var bufferSize = 4096;
191 | this.recorder = this.context.createScriptProcessor(bufferSize, this.config.channelCount, this.config.channelCount); // 第二个和第三个参数指的是输入和输出的声道数
192 | this.buffer = [];
193 | this.bufferLength = 0;
194 |
195 | return this;
196 | }
197 |
198 | this.compress = function() { //合并压缩
199 | //合并
200 | var buffer = this.buffer,
201 | bufferLength = this.bufferLength;
202 | this.buffer = []; //处理缓存并将之清空
203 | this.bufferLength = 0;
204 | var data = new Float32Array(bufferLength);
205 | for (var i = 0, offset = 0; i < buffer.length; i++) {
206 | data.set(buffer[i], offset);
207 | offset += buffer[i].length;
208 | }
209 | //压缩
210 | var config = this.config,
211 | compression = parseInt(config.inputSampleRate / config.outputSampleRate),
212 | //计算压缩率
213 | length = parseInt(data.length / compression),
214 | result = new Float32Array(length);
215 | index = 0;
216 | while (index < length) {
217 | result[index] = data[index++ * compression];
218 | }
219 | return result;
220 | }
221 |
222 | this.encodeWAV = function(bytes) {
223 | var config = this.config,
224 | sampleRate = Math.min(config.inputSampleRate, config.outputSampleRate),
225 | sampleBits = Math.min(config.inputSampleBits, config.oututSampleBits),
226 | dataLength = bytes.length * (sampleBits / 8),
227 | buffer = new ArrayBuffer(44 + dataLength),
228 | view = new DataView(buffer),
229 | channelCount = config.channelCount,
230 | offset = 0,
231 | volume = config.volume;
232 |
233 | writeUTFBytes = function(str) {
234 | for (var i = 0; i < str.length; i++) {
235 | view.setUint8(offset + i, str.charCodeAt(i));
236 | }
237 | };
238 | // 资源交换文件标识符
239 | writeUTFBytes('RIFF');
240 | offset += 4;
241 | // 下个地址开始到文件尾总字节数,即文件大小-8
242 | view.setUint32(offset, 44 + dataLength, true);
243 | offset += 4;
244 | // WAV文件标志
245 | writeUTFBytes('WAVE');
246 | offset += 4;
247 | // 波形格式标志
248 | writeUTFBytes('fmt ');
249 | offset += 4;
250 | // 过滤字节,一般为 0x10 = 16
251 | view.setUint32(offset, 16, true);
252 | offset += 4;
253 | // 格式类别 (PCM形式采样数据)
254 | view.setUint16(offset, 1, true);
255 | offset += 2;
256 | // 通道数
257 | view.setUint16(offset, channelCount, true);
258 | offset += 2;
259 | // 采样率,每秒样本数,表示每个通道的播放速度
260 | view.setUint32(offset, sampleRate, true);
261 | offset += 4;
262 | // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
263 | view.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true);
264 | offset += 4;
265 | // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
266 | view.setUint16(offset, channelCount * (sampleBits / 8), true);
267 | offset += 2;
268 | // 每样本数据位数
269 | view.setUint16(offset, sampleBits, true);
270 | offset += 2;
271 | // 数据标识符
272 | writeUTFBytes('data');
273 | offset += 4;
274 | // 采样数据总数,即数据总大小-44
275 | view.setUint32(offset, dataLength, true);
276 | offset += 4;
277 | // 写入采样数据
278 | if (sampleBits === 8) {
279 | for (var i = 0; i < bytes.length; i++, offset++) {
280 | var val = bytes[i] * (0x7FFF * volume);
281 | val = parseInt(255 / (65535 / (val + 32768)));
282 | view.setInt8(offset, val, true);
283 | }
284 | } else if (sampleBits === 16) {
285 | for (var i = 0; i < bytes.length; i++, offset += 2) {
286 | var val = bytes[i] * (0x7FFF * volume);
287 | view.setInt16(offset, val, true);
288 | }
289 | }
290 | return new Blob([view], {
291 | type: 'audio/wav'
292 | });
293 | }
294 |
295 | this.updateSource = function(callback) {
296 | if (!this.isReady) return;
297 | var _microphone = this,
298 | blob = this.encodeWAV(this.compress());
299 | url = this.src;
300 | url && window.URL && window.URL.revokeObjectURL && window.URL.revokeObjectURL(url);
301 | if (blob.size > 44) { //size为44的时候,数据部分为空
302 | this.CurrentData = blob;
303 | this.src = window.URL.createObjectURL(blob);
304 | }
305 | callback && callback();
306 | setTimeout(function() {
307 | _microphone.updateSource(callback);
308 | }, _microphone.config.cycle);
309 | }
310 |
311 | this.init();
312 | }
313 |
314 | window.Video = function(obj) {
315 |
316 | this.obj = $(obj);
317 |
318 | this.dom = this.obj.get(0);
319 |
320 | this.init = function(src) {
321 | this.canPlay = false;
322 | this.dom.src = this.src = src;
323 | return this;
324 | }
325 |
326 | this.play = function() {
327 | this.dom.play();
328 | return this;
329 | };
330 |
331 | this.pause = function() {
332 | this.dom.pause();
333 | return this;
334 | };
335 |
336 | this.CurrentFrame = function(width, height) {
337 | var _canvas = new myCanvas(),
338 | canvas = _canvas.dom,
339 | image = new Image(),
340 | ctx = canvas.getContext("2d");
341 | //重置canvans宽高
342 | canvas.width = width || this.dom.width;
343 | canvas.height = height || this.dom.height;
344 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
345 | image.src = canvas.toDataURL("image/png");
346 | _canvas.remove();
347 | _canvas = null;
348 | return image;
349 | };
350 |
351 | this.CurrentFrameData = function(width, height) {
352 | var _canvas = new myCanvas(),
353 | canvas = _canvas.dom,
354 | ctx = canvas.getContext("2d");
355 | //重置canvans宽高
356 | canvas.width = width || this.dom.width;
357 | canvas.height = height || this.dom.height;
358 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
359 | var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
360 | _canvas.remove();
361 | _canvas = null;
362 | return data;
363 | };
364 |
365 | this.CurrentBlob = function(width, height) {
366 | var _canvas = new myCanvas(),
367 | canvas = _canvas.dom,
368 | ctx = canvas.getContext("2d");
369 | //重置canvans宽高
370 | canvas.width = width || this.dom.width;
371 | canvas.height = height || this.dom.height;
372 | ctx.drawImage(this.dom, 0, 0, canvas.width, canvas.height); // 将图像绘制到canvas上
373 | var data = dataURLtoBlob(canvas.toDataURL("image/png"))
374 | _canvas.remove();
375 | _canvas = null;
376 | return data;
377 | };
378 | }
379 |
380 | window.audio = function(obj) {
381 |
382 | this.obj = $(obj);
383 |
384 | this.dom = this.obj.get(0);
385 |
386 | this.init = function(src) {
387 | if (src) {
388 | this.dom.src = this.src = src;
389 | }
390 | return this;
391 | }
392 |
393 | this.play = function() {
394 | this.dom.play();
395 | return this;
396 | };
397 |
398 | this.pause = function() {
399 | this.dom.pause();
400 | this.source && this.source.stop(this.src);
401 | return this;
402 | };
403 | }
404 |
405 | window.dataURLtoBlob = function(dataurl) {
406 | var arr = dataurl.split(','),
407 | mime = arr[0].match(/:(.*?);/)[1],
408 | bstr = atob(arr[1]),
409 | n = bstr.length,
410 | u8arr = new Uint8Array(n);
411 | while (n--) {
412 | u8arr[n] = bstr.charCodeAt(n);
413 | }
414 | return new Blob([u8arr], {
415 | type: mime
416 | });;
417 | }
418 |
419 | window.readBlobAsDataURL = function(blob, callback) {
420 | var a = new FileReader();
421 | a.onload = function(e) {
422 | callback(e.target.result);
423 | };
424 | a.readAsDataURL(blob);
425 | }
426 |
427 | window.DataURLtoString = function(dataurl) {
428 | return window.atob(dataurl.substring("data:text/plain;base64,".length))
429 | }
430 |
431 | window.myCanvas = function(width, height, isDisplay) {
432 |
433 | (function(_this) {
434 | width = width || 640, height = height || 360;
435 | display = isDisplay ? '' : ' style="display: none;"';
436 | obj = $('
');
437 | obj.appendTo("body");
438 | _this.dom = obj.get(0);
439 | })(this)
440 |
441 | this.remove = function() {
442 | $(this.dom).remove();
443 | }
444 | }
445 | })(window);
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
asd欢迎来到WebSocket聊天室
20 |
21 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 发送
43 | 关闭
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
71 |
72 |
73 |
74 |
77 |
78 |
83 |
84 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/microphoneTest1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
浏览器捕捉麦克风功能测试1
5 |
6 |
7 |
8 |
24 |
25 |
26 |
27 | 浏览器捕捉麦克风功能测试1
28 |
29 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/microphoneTest2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
浏览器捕捉麦克风功能测试2
13 |
14 |
15 |
16 | 浏览器捕捉麦克风功能测试2
17 |
18 |
19 |
20 |
21 |
22 | 点击“开始录制”按钮开始录音
23 |
24 |
25 |
213 |
214 |
215 |
216 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/Toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/Toolbar.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close_down.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close_hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/btn_close_hover.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/canvaspost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/canvaspost.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/headpic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/headpic.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_chat.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_file.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_video.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_voice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/mod_voice.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/photo_loading.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/photo_loading.jpg
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/sprite_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/sprite_main.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/videopost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-samples/Tomcat-Websocket/WebRoot/ws/pic/websocket/videopost.png
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/action/AbstractWSController.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.action;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import indi.anyesu.model.Message;
5 | import indi.anyesu.model.Message.MsgConstant;
6 | import indi.anyesu.util.StringUtil;
7 |
8 | import javax.websocket.EndpointConfig;
9 | import javax.websocket.RemoteEndpoint.Async;
10 | import javax.websocket.RemoteEndpoint.Basic;
11 | import javax.websocket.Session;
12 | import java.io.IOException;
13 | import java.nio.ByteBuffer;
14 | import java.util.List;
15 |
16 | /**
17 | * Websocket 通讯 抽象类
18 | *
19 | * @author anyesu
20 | */
21 | public abstract class AbstractWSController {
22 | private Session session;
23 | private String userName;
24 |
25 | abstract List
getConnections();
26 |
27 | abstract String getConnectType();
28 |
29 | /**
30 | * websock连接建立后触发
31 | *
32 | * @param session
33 | * @param config
34 | */
35 | protected void OnOpen(Session session, EndpointConfig config) {
36 | getConnections().add(this);
37 | broadcast2All(new Message(getUserName(), MsgConstant.Open, getUsers()).toString());
38 | System.out.println(getConnectType() + ": " + getUserName() + "加入了,当前总人数:" + getConnections().size());
39 | }
40 |
41 | /**
42 | * websock连接断开后触发
43 | */
44 | protected void OnClose() {
45 | getConnections().remove(this);
46 | broadcast2Others(new Message(getUserName(), MsgConstant.Close, getUsers()).toString());
47 | System.out.println(getConnectType() + ": " + getUserName() + "退出了,当前总人数:" + getConnections().size());
48 | }
49 |
50 | /**
51 | * 接受客户端发送的字符串
52 | *
53 | * @param message
54 | */
55 | protected void OnMessage(String message) {
56 | Message msg = JSONObject.parseObject(message, Message.class);
57 | msg.setHost(getUserName());
58 | if (getConnectType().equals("text")) {
59 | msg.setMsg(StringUtil.txt2htm(msg.getMsg()));
60 | if (msg.getDests() == null) {
61 | broadcast2All(msg.toString());
62 | } else {
63 | broadcast2Special(msg.toString(), msg.getDests());
64 | }
65 | } else {
66 | broadcast2Others(msg.toString());
67 | }
68 | }
69 |
70 | /**
71 | * 接收客户端发送的字节流
72 | *
73 | * @param message
74 | */
75 | protected void OnMessage(ByteBuffer message) {
76 | broadcast2Others(message);
77 | }
78 |
79 | /**
80 | * 发生错误
81 | */
82 | protected void OnError(Throwable t) throws Throwable {
83 | }
84 |
85 | /**
86 | * 广播给所有用户
87 | *
88 | * @param msg
89 | */
90 | protected void broadcast2All(T msg) {
91 | for (AbstractWSController client : getConnections())
92 | client.call(msg);
93 | }
94 |
95 | /**
96 | * 发送给指定的用户
97 | *
98 | * @param msg
99 | */
100 | protected void broadcast2Special(T msg, String[] dests) {
101 | for (AbstractWSController client : getConnections())
102 | if (StringUtil.Contains(dests, client.getUserName()))
103 | client.call(msg);
104 | }
105 |
106 | /**
107 | * 广播给除了自己外的用户
108 | *
109 | * @param msg
110 | */
111 | protected void broadcast2Others(T msg) {
112 | for (AbstractWSController client : getConnections())
113 | if (!client.getUserName().equals(this.getUserName()))
114 | client.call(msg);
115 | }
116 |
117 | /**
118 | * 异步方式向客户端发送字符串
119 | *
120 | * @param msg
121 | * 参数类型为String或ByteBuffer
122 | */
123 | protected void callAsync(T msg) {
124 | Async remote = this.getSession().getAsyncRemote();
125 | if (msg instanceof String) {
126 | remote.sendText((String) msg);
127 | } else if (msg instanceof ByteBuffer) {
128 | remote.sendBinary((ByteBuffer) msg);
129 | }
130 | }
131 |
132 | /**
133 | * 同步方式向客户端发送字符串
134 | *
135 | * @param msg
136 | * 参数类型为String或ByteBuffer
137 | */
138 | protected void call(T msg) {
139 | try {
140 | synchronized (this) {
141 | Basic remote = this.getSession().getBasicRemote();
142 | if (msg instanceof String) {
143 | remote.sendText((String) msg);
144 | } else if (msg instanceof ByteBuffer) {
145 | remote.sendBinary((ByteBuffer) msg);
146 | }
147 |
148 | }
149 | } catch (IOException e) {
150 | try {
151 | this.getSession().close();
152 | } catch (IOException e1) {
153 | // Ignore
154 | }
155 | OnClose();
156 | }
157 | }
158 |
159 | protected void setSession(Session session) {
160 | this.session = session;
161 | }
162 |
163 | protected Session getSession() {
164 | return this.session;
165 | }
166 |
167 | protected String getUserName() {
168 | return userName;
169 | }
170 |
171 | protected void setUserName(String userName) {
172 | this.userName = userName;
173 | }
174 |
175 | protected String[] getUsers() {
176 | int i = 0;
177 | String[] destArrary = new String[getConnections().size()];
178 | for (AbstractWSController client : getConnections())
179 | destArrary[i++] = client.getUserName();
180 | return destArrary;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/action/AudioController.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.action;
2 |
3 | import javax.websocket.EndpointConfig;
4 | import javax.websocket.OnClose;
5 | import javax.websocket.OnError;
6 | import javax.websocket.OnMessage;
7 | import javax.websocket.OnOpen;
8 | import javax.websocket.Session;
9 | import javax.websocket.server.ServerEndpoint;
10 | import java.io.IOException;
11 | import java.nio.ByteBuffer;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.concurrent.CopyOnWriteArrayList;
15 |
16 | /**
17 | * Websocket 音频通讯
18 | *
19 | * @author anyesu
20 | */
21 | @ServerEndpoint(value = "/websocket/chat/audio", configurator = wsConfigurator.class)
22 | public class AudioController extends AbstractWSController {
23 | private static final List connections = new CopyOnWriteArrayList();
24 |
25 | @OnOpen
26 | public void OnOpen(Session session, EndpointConfig config) {
27 | // 设置用户信息
28 | Map> map = session.getRequestParameterMap();
29 | setSession(session);
30 | if (map.get("uid") == null) {
31 | try {
32 | this.getSession().close();
33 | } catch (IOException e) {
34 | }
35 | }
36 | setUserName(map.get("uid").get(0));
37 | super.OnOpen(session, config);
38 | }
39 |
40 | @OnClose
41 | public void OnClose() {
42 | super.OnClose();
43 | }
44 |
45 | @OnMessage(maxMessageSize = 10000000)
46 | public void OnMessage(String message) {
47 | super.OnMessage(message);
48 | }
49 |
50 | @OnMessage(maxMessageSize = 10000000)
51 | public void OnMessage(ByteBuffer message) {
52 | super.OnMessage(message);
53 | }
54 |
55 | @OnError
56 | public void OnError(Throwable t) throws Throwable {
57 | }
58 |
59 | @Override
60 | List getConnections() {
61 | return connections;
62 | }
63 |
64 | @Override
65 | String getConnectType() {
66 | return "audio";
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/action/TextController.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.action;
2 |
3 | import indi.anyesu.model.IdGenerator;
4 | import indi.anyesu.model.Message;
5 | import indi.anyesu.model.Message.MsgConstant;
6 | import indi.anyesu.model.Message.RoomInfo;
7 |
8 | import javax.websocket.EndpointConfig;
9 | import javax.websocket.OnClose;
10 | import javax.websocket.OnError;
11 | import javax.websocket.OnMessage;
12 | import javax.websocket.OnOpen;
13 | import javax.websocket.Session;
14 | import javax.websocket.server.ServerEndpoint;
15 | import java.nio.ByteBuffer;
16 | import java.text.SimpleDateFormat;
17 | import java.util.Date;
18 | import java.util.Iterator;
19 | import java.util.List;
20 | import java.util.concurrent.CopyOnWriteArrayList;
21 |
22 | /**
23 | * Websocket 文字通讯
24 | *
25 | * @author anyesu
26 | */
27 | @ServerEndpoint(value = "/websocket/chat", configurator = wsConfigurator.class)
28 | public class TextController extends AbstractWSController {
29 | private static final List connections = new CopyOnWriteArrayList();
30 |
31 | private RoomInfo roomInfo;
32 |
33 | @OnOpen
34 | public void OnOpen(Session session, EndpointConfig config) {
35 | // 设置用户信息
36 | setUserName(IdGenerator.getNextId());
37 | setSession(session);
38 | // 设置聊天室信息
39 | if (connections.size() == 0) {
40 | setRoomInfo(new RoomInfo(getUserName(), (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date())));
41 | } else {
42 | Iterator it = connections.iterator();
43 | TextController client = (TextController) it.next();
44 | setRoomInfo(client.getRoomInfo());
45 | }
46 | Message msg = new Message(getUserName(), MsgConstant.setName);
47 | msg.setRoomInfo(getRoomInfo());
48 | call(msg.toString());
49 | super.OnOpen(session, config);
50 | }
51 |
52 | @OnClose
53 | public void OnClose() {
54 | super.OnClose();
55 | }
56 |
57 | @OnMessage(maxMessageSize = 10000000)
58 | public void OnMessage(String message) {
59 | super.OnMessage(message);
60 | }
61 |
62 | @OnMessage(maxMessageSize = 10000000)
63 | public void OnMessage(ByteBuffer message) {
64 | super.OnMessage(message);
65 | }
66 |
67 | @OnError
68 | public void OnError(Throwable t) throws Throwable {
69 | }
70 |
71 | @Override
72 | List getConnections() {
73 | return connections;
74 | }
75 |
76 | /**
77 | * 设置聊天室信息
78 | */
79 | private void setRoomInfo(RoomInfo roomInfo) {
80 | this.roomInfo = roomInfo;
81 | }
82 |
83 | private RoomInfo getRoomInfo() {
84 | return roomInfo;
85 | }
86 |
87 | @Override
88 | String getConnectType() {
89 | return "text";
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/action/VideoController.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.action;
2 |
3 | import javax.websocket.EndpointConfig;
4 | import javax.websocket.OnClose;
5 | import javax.websocket.OnError;
6 | import javax.websocket.OnMessage;
7 | import javax.websocket.OnOpen;
8 | import javax.websocket.Session;
9 | import javax.websocket.server.ServerEndpoint;
10 | import java.io.IOException;
11 | import java.nio.ByteBuffer;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.concurrent.CopyOnWriteArrayList;
15 |
16 | /**
17 | * Websocket 视频通讯
18 | *
19 | * @author anyesu
20 | */
21 | @ServerEndpoint(value = "/websocket/chat/video", configurator = wsConfigurator.class)
22 | public class VideoController extends AbstractWSController {
23 | private static final List connections = new CopyOnWriteArrayList();
24 |
25 | @OnOpen
26 | public void OnOpen(Session session, EndpointConfig config) {
27 | // 设置用户信息
28 | Map> map = session.getRequestParameterMap();
29 | setSession(session);
30 | if (map.get("uid") == null) {
31 | try {
32 | this.getSession().close();
33 | } catch (IOException e) {
34 | }
35 | }
36 | setUserName(map.get("uid").get(0));
37 | super.OnOpen(session, config);
38 | }
39 |
40 | @OnClose
41 | public void OnClose() {
42 | super.OnClose();
43 | }
44 |
45 | @OnMessage(maxMessageSize = 10000000)
46 | public void OnMessage(String message) {
47 | super.OnMessage(message);
48 | }
49 |
50 | @OnMessage(maxMessageSize = 10000000)
51 | public void OnMessage(ByteBuffer message) {
52 | super.OnMessage(message);
53 | }
54 |
55 | @OnError
56 | public void OnError(Throwable t) throws Throwable {
57 | }
58 |
59 | @Override
60 | List getConnections() {
61 | return connections;
62 | }
63 |
64 | @Override
65 | String getConnectType() {
66 | return "video";
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/action/wsConfigurator.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.action;
2 |
3 | import javax.servlet.http.HttpSession;
4 | import javax.websocket.HandshakeResponse;
5 | import javax.websocket.server.HandshakeRequest;
6 | import javax.websocket.server.ServerEndpointConfig;
7 |
8 | public class wsConfigurator extends ServerEndpointConfig.Configurator {
9 | @Override
10 | public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
11 | // 通过配置来获取httpsession
12 | HttpSession httpSession = (HttpSession) request.getHttpSession();
13 | if (httpSession != null) {
14 | config.getUserProperties().put(HttpSession.class.getName(), httpSession);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/model/IdGenerator.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.model;
2 |
3 | import java.util.concurrent.locks.Lock;
4 | import java.util.concurrent.locks.ReentrantLock;
5 |
6 | /**
7 | * 获取随机id (时间戳 + 一位数字序号)
8 | *
9 | * @author anyesu
10 | *
11 | */
12 | public class IdGenerator {
13 | private static final long LIMIT = 10;
14 | private static final Lock LOCK = new ReentrantLock();
15 | private static long LastTime = System.currentTimeMillis();
16 | private static int COUNT = 0;
17 |
18 | public static synchronized String getNextId() {
19 | LOCK.lock();
20 | try {
21 | while (true) {
22 | long now = System.currentTimeMillis();
23 | if (now == LastTime) {
24 | if (++COUNT == LIMIT) {
25 | try {
26 | Thread.currentThread();
27 | Thread.sleep(1);
28 | } catch (java.lang.InterruptedException e) {
29 | }
30 | continue;
31 | }
32 | } else {
33 | LastTime = now;
34 | COUNT = 0;
35 | }
36 | break;
37 | }
38 | } finally {
39 | LOCK.unlock();
40 | }
41 | return LastTime + "" + COUNT;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/model/Message.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.model;
2 |
3 | import java.util.Arrays;
4 |
5 | import com.alibaba.fastjson.JSONObject;
6 |
7 | public class Message {
8 | private int type;// 消息类型
9 |
10 | private String msg;// 消息主题
11 |
12 | private String host;// 发送者
13 |
14 | private String[] dests;// 接受者
15 |
16 | private RoomInfo roomInfo;// 聊天室信息
17 |
18 | public class MsgConstant {
19 | public final static int Open = 1;// 新连接
20 | public final static int Close = 2;// 连接断开
21 | public final static int MsgToAll = 3;// 发送给所有人
22 | public final static int MsgToPoints = 4;// 发送给指定用户
23 | public final static int RequireLogin = 5;// 需要登录
24 | public final static int setName = 6;// 设置用户名
25 | }
26 |
27 | public static class RoomInfo {
28 | private String name;// 聊天室名称
29 | private String creater;// 创建人
30 | private String createTime;// 创建时间
31 |
32 | public RoomInfo(String creater, String createTime) {
33 | this.creater = creater;
34 | this.createTime = createTime;
35 | }
36 |
37 | public RoomInfo(String name) {
38 | this.name = name;
39 | }
40 |
41 | public String getName() {
42 | return name;
43 | }
44 |
45 | public void setName(String name) {
46 | this.name = name;
47 | }
48 |
49 | public String getCreater() {
50 | return creater;
51 | }
52 |
53 | public void setCreater(String creater) {
54 | this.creater = creater;
55 | }
56 |
57 | public String getCreateTime() {
58 | return createTime;
59 | }
60 |
61 | public void setCreateTime(String createTime) {
62 | this.createTime = createTime;
63 | }
64 | }
65 |
66 | public Message() {
67 | setType(MsgConstant.MsgToAll);
68 | }
69 |
70 | public Message(String host, int type) {
71 | setHost(host);
72 | setType(type);
73 | }
74 |
75 | public Message(String host, int type, String msg) {
76 | this(host, type);
77 | setMsg(msg);
78 | }
79 |
80 | public Message(String host, int type, String[] dests) {
81 | this(host, type);
82 | setDests(dests);
83 | }
84 |
85 | @Override
86 | public String toString() {
87 | // 序列化成json串
88 | return JSONObject.toJSONString(this);
89 | }
90 |
91 | public String toString2() {
92 | StringBuilder builder = new StringBuilder();
93 | builder.append("Message [type=").append(type).append(", msg=").append(msg).append(", host=").append(host).append(", dests=").append(Arrays.toString(dests)).append(", roomInfo=")
94 | .append(roomInfo).append("]");
95 | return builder.toString();
96 | }
97 |
98 | public int getType() {
99 | return type;
100 | }
101 |
102 | public void setType(int type) {
103 | this.type = type;
104 | }
105 |
106 | public String getMsg() {
107 | return msg;
108 | }
109 |
110 | public void setMsg(String msg) {
111 | this.msg = msg;
112 | }
113 |
114 | public String getHost() {
115 | return host;
116 | }
117 |
118 | public void setHost(String host) {
119 | this.host = host;
120 | }
121 |
122 | public String[] getDests() {
123 | return dests;
124 | }
125 |
126 | public void setDests(String[] dests) {
127 | this.dests = dests;
128 | }
129 |
130 | public RoomInfo getRoomInfo() {
131 | return roomInfo;
132 | }
133 |
134 | public void setRoomInfo(RoomInfo roomInfo) {
135 | this.roomInfo = roomInfo;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/websocket-samples/Tomcat-Websocket/src/indi/anyesu/util/StringUtil.java:
--------------------------------------------------------------------------------
1 | package indi.anyesu.util;
2 |
3 | import java.text.DecimalFormat;
4 |
5 | public class StringUtil {
6 | public static String obj2String(Object obj, String defVal) {
7 | if (obj instanceof String)
8 | return (String) obj;
9 | return defVal;
10 | }
11 |
12 | /**
13 | * @param s1
14 | * @param s2
15 | * @param specialStr
16 | * @return 1.如果s2为null,返回false 2、如果s2等于specialStr,直接返回true
17 | * 3、如果s1等于null,返回false 4、如果s1包含s2,返回true 5、返回false
18 | *
19 | */
20 | public static boolean contain(String s1, String s2, String specialStr) {
21 | if (null == s2)
22 | return false;
23 | if (s2.equals(specialStr))
24 | return true;
25 | if (null == s1)
26 | return false;
27 | return s1.contains(s2);
28 | }
29 |
30 | public static String getString(Object str) {
31 | if (str == null) {
32 | str = "";
33 | }
34 | return str.toString();
35 | }
36 |
37 | public static String substring(String str, int len, String subfix) {
38 | if (null == str)
39 | str = "";
40 | if (str.length() > len) {
41 | if (null == subfix)
42 | subfix = "";
43 | return str.substring(0, len) + subfix;
44 | }
45 | return str;
46 | }
47 |
48 | public static String changeToHTML(String str) {
49 | if (null != str) {
50 | String retStr = str.replace("\r\n", "
");
51 | // str.replace("\r", "
");
52 | // str.replace("\n", "
");
53 | return retStr;
54 | }
55 | return "";
56 | }
57 |
58 | /**
59 | * @param d
60 | * 要格式化的数字
61 | * @param parttern
62 | * 要求格式结果,例如:0.00
63 | * @return
64 | */
65 | public static String formatNum(double d, String parttern) {
66 | DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance();
67 | df.applyPattern(parttern);
68 | return df.format(d);
69 | }
70 |
71 | // 判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
72 | public static boolean isNotBlank(String str) {
73 | int length;
74 | if (str == null)
75 | return false;
76 |
77 | if ((length = str.length()) == 0)
78 | return false;
79 | for (int i = 0; i < length; ++i) {
80 | if (Character.isWhitespace(str.charAt(i)))
81 | return false;
82 | }
83 | return true;
84 | }
85 |
86 | // 判断某字符串是否为空或长度为0或由空白符(whitespace) 构成
87 | public static boolean isBlank(String str) {
88 | int length;
89 | if (str == null)
90 | return true;
91 |
92 | if ((length = str.length()) == 0)
93 | return true;
94 | for (int i = 0; i < length; ++i) {
95 | if (Character.isWhitespace(str.charAt(i)))
96 | return true;
97 | }
98 | return false;
99 | }
100 |
101 | public static boolean isEmpty(String str) {
102 | if (null == str || str.length() == 0)
103 | return true;
104 | return false;
105 | }
106 |
107 | public static boolean isNotEmpty(String str) {
108 | if (null == str || str.length() == 0)
109 | return false;
110 | return true;
111 | }
112 |
113 | /**
114 | * html转文本
115 | *
116 | * @param htm
117 | * @return
118 | */
119 | public static String htm2txt(String htm) {
120 | if (StringUtil.isBlank(htm)) {
121 | return htm;
122 | }
123 | return htm.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll(" ", " ").replaceAll("
", "\n").replaceAll("'", "\'");
124 | }
125 |
126 | /**
127 | * html代码转义
128 | *
129 | * @param txt
130 | * @return
131 | */
132 | public static String txt2htm(String txt) {
133 | if (StringUtil.isBlank(txt)) {
134 | return txt;
135 | }
136 | return txt.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll(" ", " ").replaceAll("\n", "
").replaceAll("\'", "'");
137 | }
138 |
139 | public static boolean Contains(String[] strs, String str) {
140 | if (isEmpty(str) || strs.length == 0)
141 | return false;
142 | for (String s : strs)
143 | if (s.equals(str))
144 | return true;
145 | return false;
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/websocket-samples/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # tomcat版
2 | demo-websocket-tomcat:
3 | # 指定用于构建镜像的Dockerfile路径, 值为字符串
4 | build: 'Tomcat-Websocket'
5 | # 设置容器用户名(镜像中已创建),默认root
6 | user: user_docker
7 | # 设置容器主机名
8 | hostname: docker-anyesu
9 | # 容器内root账户是否拥有宿主机root账户的所有权限
10 | privileged: false
11 | # 当容器退出时docker自动重启它
12 | restart: always
13 | # 与宿主机之间的端口映射
14 | ports:
15 | - 8080:8080
16 | # 设置容器环境变量
17 | environment:
18 | JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom
19 |
20 | # nodejs版
21 | demo-websocket-nodejs:
22 | # 指定用于构建镜像的Dockerfile路径, 值为字符串
23 | build: 'Nodejs-Websocket'
24 | privileged: false
25 | restart: always
26 | ports:
27 | - 3000:3000
28 | - 3002:3002
--------------------------------------------------------------------------------
/websocket-samples/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | indi.anyesu.websocket.samples
6 | websocket-samples
7 | pom
8 | 1.0-SNAPSHOT
9 | websocket-samples
10 |
11 |
12 | indi.anyesu.websocket
13 | websocket-parent
14 | 1.0-SNAPSHOT
15 | ../websocket-parent/pom.xml
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------