├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── websocket-chat ├── pom.xml └── src │ └── main │ ├── java │ └── indi │ │ └── anyesu │ │ └── action │ │ ├── AudioController.java │ │ ├── BaseController.java │ │ ├── BaseMediaController.java │ │ ├── TextController.java │ │ ├── VideoController.java │ │ └── WsConfigurator.java │ └── webapp │ ├── META-INF │ └── MANIFEST.MF │ ├── WEB-INF │ └── web.xml │ ├── index.html │ └── ws │ ├── cameraTest.html │ ├── css │ └── chat.css │ ├── js │ ├── WSClient.js │ ├── app.js │ ├── chat.js │ ├── jquery-1.9.1.js │ ├── jquery.nicescroll.min.js │ └── media.js │ ├── main.html │ ├── microphoneTest1.html │ ├── microphoneTest2.html │ └── pic │ └── websocket │ ├── Toolbar.png │ ├── btn_close.png │ ├── btn_close_down.png │ ├── btn_close_hover.png │ ├── canvaspost.png │ ├── headpic.png │ ├── mod_chat.png │ ├── mod_file.png │ ├── mod_video.png │ ├── mod_voice.png │ ├── photo_loading.jpg │ ├── sprite_main.png │ └── videopost.png ├── websocket-core ├── pom.xml └── src │ └── main │ └── java │ └── indi │ └── anyesu │ ├── action │ └── AbstractWsController.java │ ├── model │ ├── IdGenerator.java │ └── Message.java │ └── util │ └── StringUtil.java ├── websocket-parent └── pom.xml └── websocket-samples ├── Nodejs-Websocket ├── Dockerfile ├── cameraTest.html ├── css │ └── chat.css ├── index.html ├── js │ ├── WSClient.js │ ├── app.js │ ├── chat.js │ ├── jquery-1.9.1.js │ ├── jquery.nicescroll.min.js │ ├── latest-v2.js │ └── media.js ├── microphoneTest1.html ├── microphoneTest2.html ├── pic │ └── websocket │ │ ├── Toolbar.png │ │ ├── btn_close.png │ │ ├── btn_close_down.png │ │ ├── btn_close_hover.png │ │ ├── canvaspost.png │ │ ├── headpic.png │ │ ├── mod_chat.png │ │ ├── mod_file.png │ │ ├── mod_video.png │ │ ├── mod_voice.png │ │ ├── sprite_main.png │ │ └── videopost.png ├── server.js └── 启动服务.bat ├── README.md ├── Tomcat-Websocket ├── Dockerfile ├── WebRoot │ ├── META-INF │ │ └── MANIFEST.MF │ ├── WEB-INF │ │ ├── lib │ │ │ └── fastjson-1.1.41.jar │ │ └── web.xml │ ├── index.html │ └── ws │ │ ├── cameraTest.html │ │ ├── css │ │ └── chat.css │ │ ├── js │ │ ├── WSClient.js │ │ ├── app.js │ │ ├── chat.js │ │ ├── jquery-1.9.1.js │ │ ├── jquery.nicescroll.min.js │ │ └── media.js │ │ ├── main.html │ │ ├── microphoneTest1.html │ │ ├── microphoneTest2.html │ │ └── pic │ │ └── websocket │ │ ├── Toolbar.png │ │ ├── btn_close.png │ │ ├── btn_close_down.png │ │ ├── btn_close_hover.png │ │ ├── canvaspost.png │ │ ├── headpic.png │ │ ├── mod_chat.png │ │ ├── mod_file.png │ │ ├── mod_video.png │ │ ├── mod_voice.png │ │ ├── photo_loading.jpg │ │ ├── sprite_main.png │ │ └── videopost.png └── src │ └── indi │ └── anyesu │ ├── action │ ├── AbstractWSController.java │ ├── AudioController.java │ ├── TextController.java │ ├── VideoController.java │ └── wsConfigurator.java │ ├── model │ ├── IdGenerator.java │ └── Message.java │ └── util │ └── StringUtil.java ├── docker-compose.yml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files and Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | 12 | # Compiled class files 13 | *.class 14 | 15 | # Log Files 16 | *.log 17 | 18 | # Eclipse Project Files 19 | 20 | .classpath 21 | .project 22 | .settings/ 23 | 24 | # IntelliJ IDEA Files 25 | 26 | *.iml 27 | *.ipr 28 | *.iws 29 | *.idea 30 | /.idea/ 31 | /out/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present anyesu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # websocket应用 2 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | websocket 6 | websocket 7 | pom 8 | 9 | 10 | indi.anyesu.websocket 11 | websocket-parent 12 | 1.0-SNAPSHOT 13 | websocket-parent/pom.xml 14 | 15 | 16 | 17 | websocket-core 18 | websocket-chat 19 | 20 | -------------------------------------------------------------------------------- /websocket-chat/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | websocket-chat 6 | war 7 | 1.0-SNAPSHOT 8 | websocket-chat 9 | 10 | 11 | indi.anyesu.websocket 12 | websocket-parent 13 | 1.0-SNAPSHOT 14 | ../websocket-parent/pom.xml 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | javax.servlet 23 | servlet-api 24 | 2.5 25 | provided 26 | 27 | 28 | 29 | javax.websocket 30 | javax.websocket-api 31 | 1.1 32 | provided 33 | 34 | 35 | 36 | indi.anyesu.websocket 37 | websocket-core 38 | 1.0-SNAPSHOT 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/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.nio.ByteBuffer; 11 | import java.util.List; 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | 14 | /** 15 | * Websocket 音频通讯 16 | * 17 | * @author anyesu 18 | */ 19 | @ServerEndpoint(value = "/websocket/chat/audio", configurator = WsConfigurator.class) 20 | public class AudioController extends BaseMediaController { 21 | 22 | private static final List CONNECTIONS = new CopyOnWriteArrayList<>(); 23 | 24 | @Override 25 | @OnOpen 26 | public void onOpen(Session session, EndpointConfig config) { 27 | super.onOpen(session, config); 28 | } 29 | 30 | @Override 31 | @OnClose 32 | public void onClose() { 33 | super.onClose(); 34 | } 35 | 36 | @Override 37 | @OnMessage(maxMessageSize = 10000000) 38 | public void onMessage(String message) { 39 | super.onMessage(message); 40 | } 41 | 42 | @Override 43 | @OnMessage(maxMessageSize = 10000000) 44 | public void onMessage(ByteBuffer message) { 45 | super.onMessage(message); 46 | } 47 | 48 | @Override 49 | @OnError 50 | public void onError(Throwable t) { 51 | } 52 | 53 | @Override 54 | List getConnections() { 55 | return CONNECTIONS; 56 | } 57 | 58 | @Override 59 | String getConnectType() { 60 | return "audio"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/indi/anyesu/action/BaseController.java: -------------------------------------------------------------------------------- 1 | package indi.anyesu.action; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import indi.anyesu.model.Message; 5 | import indi.anyesu.util.StringUtil; 6 | 7 | /** 8 | * 公共逻辑 9 | * 10 | * @author anyesu 11 | */ 12 | public abstract class BaseController extends AbstractWsController { 13 | 14 | private static final String CONNECT_TYPE_TEXT = "text"; 15 | 16 | /** 17 | * 接受客户端发送的字符串 18 | * 19 | * @param message 字符串消息 20 | */ 21 | @Override 22 | protected void onMessage(String message) { 23 | Message msg = JSONObject.parseObject(message, Message.class); 24 | msg.setHost(getUserName()); 25 | if (CONNECT_TYPE_TEXT.equals(getConnectType())) { 26 | msg.setMsg(StringUtil.txt2htm(msg.getMsg())); 27 | if (msg.getDests() == null) { 28 | broadcast2All(msg.toString()); 29 | } else { 30 | broadcast2Special(msg.toString(), msg.getDests()); 31 | } 32 | } else { 33 | broadcast2Others(msg.toString()); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/indi/anyesu/action/BaseMediaController.java: -------------------------------------------------------------------------------- 1 | package indi.anyesu.action; 2 | 3 | import javax.websocket.EndpointConfig; 4 | import javax.websocket.Session; 5 | import java.io.IOException; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * 公共逻辑 11 | * 12 | * @author anyesu 13 | */ 14 | public abstract class BaseMediaController extends BaseController { 15 | 16 | @Override 17 | public void onOpen(Session session, EndpointConfig config) { 18 | // 设置用户信息 19 | Map> map = session.getRequestParameterMap(); 20 | setSession(session); 21 | List uids = map.get("uid"); 22 | if (uids == null) { 23 | try { 24 | this.getSession().close(); 25 | } catch (IOException ignored) { 26 | } 27 | } else { 28 | setUserName(uids.get(0)); 29 | super.onOpen(session, config); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/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 BaseController { 29 | 30 | private static final List CONNECTIONS = new CopyOnWriteArrayList<>(); 31 | 32 | private RoomInfo roomInfo; 33 | 34 | @Override 35 | @OnOpen 36 | public void onOpen(Session session, EndpointConfig config) { 37 | // 设置用户信息 38 | setUserName(IdGenerator.getNextId()); 39 | setSession(session); 40 | // 设置聊天室信息 41 | if (CONNECTIONS.size() == 0) { 42 | setRoomInfo(new RoomInfo(getUserName(), (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()))); 43 | } else { 44 | Iterator it = CONNECTIONS.iterator(); 45 | TextController client = (TextController) it.next(); 46 | setRoomInfo(client.getRoomInfo()); 47 | } 48 | Message msg = new Message(getUserName(), MsgConstant.SET_NAME); 49 | msg.setRoomInfo(getRoomInfo()); 50 | call(msg.toString()); 51 | super.onOpen(session, config); 52 | } 53 | 54 | @Override 55 | @OnClose 56 | public void onClose() { 57 | super.onClose(); 58 | } 59 | 60 | @Override 61 | @OnMessage(maxMessageSize = 10000000) 62 | public void onMessage(String message) { 63 | super.onMessage(message); 64 | } 65 | 66 | @Override 67 | @OnMessage(maxMessageSize = 10000000) 68 | public void onMessage(ByteBuffer message) { 69 | super.onMessage(message); 70 | } 71 | 72 | @Override 73 | @OnError 74 | public void onError(Throwable t) { 75 | } 76 | 77 | @Override 78 | List getConnections() { 79 | return CONNECTIONS; 80 | } 81 | 82 | /** 83 | * 设置聊天室信息 84 | */ 85 | private void setRoomInfo(RoomInfo roomInfo) { 86 | this.roomInfo = roomInfo; 87 | } 88 | 89 | private RoomInfo getRoomInfo() { 90 | return roomInfo; 91 | } 92 | 93 | @Override 94 | String getConnectType() { 95 | return "text"; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/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.nio.ByteBuffer; 11 | import java.util.List; 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | 14 | /** 15 | * Websocket 视频通讯 16 | * 17 | * @author anyesu 18 | */ 19 | @ServerEndpoint(value = "/websocket/chat/video", configurator = WsConfigurator.class) 20 | public class VideoController extends BaseMediaController { 21 | 22 | private static final List CONNECTIONS = new CopyOnWriteArrayList<>(); 23 | 24 | @Override 25 | @OnOpen 26 | public void onOpen(Session session, EndpointConfig config) { 27 | super.onOpen(session, config); 28 | } 29 | 30 | @Override 31 | @OnClose 32 | public void onClose() { 33 | super.onClose(); 34 | } 35 | 36 | @Override 37 | @OnMessage(maxMessageSize = 10000000) 38 | public void onMessage(String message) { 39 | super.onMessage(message); 40 | } 41 | 42 | @Override 43 | @OnMessage(maxMessageSize = 10000000) 44 | public void onMessage(ByteBuffer message) { 45 | super.onMessage(message); 46 | } 47 | 48 | @Override 49 | @OnError 50 | public void onError(Throwable t) { 51 | } 52 | 53 | @Override 54 | List getConnections() { 55 | return CONNECTIONS; 56 | } 57 | 58 | @Override 59 | String getConnectType() { 60 | return "video"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /websocket-chat/src/main/java/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 | /** 9 | * @author anyesu 10 | */ 11 | public class WsConfigurator extends ServerEndpointConfig.Configurator { 12 | @Override 13 | public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { 14 | // 通过配置来获取httpSession 15 | HttpSession httpSession = (HttpSession) request.getHttpSession(); 16 | if (httpSession != null) { 17 | config.getUserProperties().put(HttpSession.class.getName(), httpSession); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | websocket-chat 4 | 5 | index.html 6 | index.htm 7 | index.jsp 8 | default.html 9 | default.htm 10 | default.jsp 11 | 12 | 13 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/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 | 73 | 不支持canvas标签 74 | 75 | 76 |
77 | 78 | 79 | 80 |
81 | 82 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/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-chat/src/main/webapp/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 | 22 | if (option.binaryType === "arraybuffer") { 23 | socket.binaryType = option.binaryType; 24 | } 25 | return socket 26 | }, 27 | init = function(client, option) { 28 | client.socket = null; 29 | client.online = false; 30 | client.isUserClose = false; 31 | client.option = option || {}; 32 | client.autoReconnect = client.option.autoReconnect || true 33 | }; 34 | this.connect = function(host) { 35 | host = host || this.option.host; 36 | var client = this, 37 | option = client.option, 38 | socket = getWebSocket(host); 39 | if (socket == null) { 40 | console.log('错误: 当前浏览器不支持WebSocket,请更换其他浏览器', true); 41 | alert('错误: 当前浏览器不支持WebSocket,请更换其他浏览器'); 42 | return 43 | } 44 | socket.onopen = function() { 45 | client.online = true; 46 | 47 | var onopen = option.onopen, 48 | type = types[option.type]; 49 | console.log("WebSocket已连接.%c类型:" + type, "color:rgb(228, 186, 20)"); 50 | onopen && onopen() 51 | }; 52 | socket.onclose = function() { 53 | var onclose = option.onclose, 54 | type = types[option.type]; 55 | client.online = false; 56 | console.log("WebSocket已断开.%c类型:" + type, "color:rgb(228, 186, 20)"); 57 | onclose && onclose(); 58 | if (!client.isUserClose && option.autoReconnect) { 59 | client.initialize() 60 | } 61 | }; 62 | socket.onmessage = function(message) { 63 | if (typeof(message.data) == "string") { 64 | var msg = JSON.parse(message.data); 65 | switch (msg.type) { 66 | case WS_Open: 67 | option.wsonopen && option.wsonopen(msg); 68 | break; 69 | case WS_Close: 70 | option.wsonclose && option.wsonclose(msg); 71 | break; 72 | case WS_MsgToAll: 73 | case WS_MsgToPoints: 74 | option.wsonmessage && option.wsonmessage(msg); 75 | break; 76 | case WS_RequireLogin: 77 | option.wsrequirelogin && option.wsrequirelogin(); 78 | break; 79 | case WS_setName: 80 | option.userName = msg.host; 81 | option.wssetname && option.wssetname(msg); 82 | break 83 | } 84 | } else if (message.data instanceof Blob) { 85 | option.wsonblob && option.wsonblob(message) 86 | } else if (message.data instanceof ArrayBuffer) { 87 | option.onWsBuffer && option.onWsBuffer(message.data); 88 | } 89 | }; 90 | isReady = true; 91 | this.socket = socket; 92 | return this 93 | }; 94 | this.initialize = function(param) { 95 | return this.connect(this.option.host + (param ? "?" + param : "")) 96 | }; 97 | this.sendString = function(message) { 98 | return isReady && this.socket.send(message) 99 | }; 100 | this.send = function(message) { 101 | return isReady && this.socket.send(message) 102 | }; 103 | this.sendBlob = function(blob) { 104 | blob = blob.appendAtFirst(this.option.userName); 105 | var result = isReady && this.socket.send(blob); 106 | blob = null; 107 | return result; 108 | }; 109 | this.close = function() { 110 | isReady = false; 111 | this.online = false; 112 | this.isUserClose = true; 113 | if (this.socket) { 114 | this.socket.close(); 115 | this.socket = null; 116 | } 117 | return true; 118 | }; 119 | this.isMe = function(name) { 120 | return this.option.userName == name 121 | }; 122 | init(this, option); 123 | } 124 | })(window); -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/js/app.js: -------------------------------------------------------------------------------- 1 | $(document.body).ready(function() { 2 | NO_SOURCE.src = "/ws/pic/websocket/canvaspost.png"; 3 | textClient.initialize(); 4 | Console.setMode(Console.ChatMode); 5 | $(".pageTop .func a").on("click", function() { 6 | if ($(this).index() < 2) { 7 | if ($(this).attr("class").indexOf("click") > -1) return; 8 | $(".pageTop .func a.click").removeClass("click"); 9 | $(this).addClass("click"); 10 | Console.setMode($(this).index()) 11 | } 12 | }); 13 | $(".pageTop .func a.voice").on("click", function() { 14 | if ($(this).attr("class").indexOf("check") > -1) { 15 | $(this).removeClass("check"); 16 | audioClient.close(); 17 | } else { 18 | if (!audioClient.online) { 19 | audioClient.initialize("uid=" + audioClient.option.userName) 20 | } 21 | $(this).addClass("check") 22 | } 23 | }); 24 | $(".mwd>:not(.pageLeft)").each(function() { 25 | this.onselectstart = function() { 26 | return false 27 | } 28 | }); 29 | var ScrollConfig = { 30 | cursorcolor: "#ffdb51", 31 | cursoropacitymax: 0.5, 32 | cursorwidth: "5PX", 33 | cursorborder: "0px solid #000", 34 | grabcursorenabled: false, 35 | preservenativescrolling: false, 36 | nativeparentscrolling: true, 37 | enablescrollonselection: true 38 | }; 39 | $("[MyScroll]").each(function() { 40 | $(this).niceScroll(ScrollConfig) 41 | }); 42 | $(window).resize(function() { 43 | if (Console.fullScreen) Console.resize() 44 | }); 45 | $(".pageTop .toolbar .min").bind("click", Console.toggleMin); 46 | $(".pageTop .toolbar .max").bind("click", Console.toggleFullScreen); 47 | $(".pageTop .toolbar .close").bind("click", Console.close); 48 | $("#min-max .back").bind("click", Console.toggleMin); 49 | $("#min-max .max").bind("click", Console.minToMax); 50 | $("#min-max .close").bind("click", Console.close); 51 | $(".mwd .pageLeft .edit .buttons .close").bind("click", Console.close) 52 | }); 53 | 54 | function CloseWindow() { 55 | if (typeof(WeixinJSBridge) != "undefined") { 56 | WeixinJSBridge.call('closeWindow') 57 | } else { 58 | var opened = window.open('about:blank', '_self'); 59 | opened.opener = null; 60 | opened.close() 61 | } 62 | } 63 | function StringBuffer(str) { 64 | this.strArray = new Array(); 65 | this.strArray.push(str); 66 | this.append = function(appendStr) { 67 | this.strArray.push(appendStr) 68 | }; 69 | this.toString = function() { 70 | return this.strArray.join("") 71 | } 72 | } 73 | $.fn.ctrlEnter = function(btns, fn) { 74 | var thiz = $(this); 75 | btns = $(btns); 76 | 77 | function performAction(e) { 78 | fn.call(thiz, e) 79 | } 80 | thiz.off("keydown").on("keydown", function(e) { 81 | if (e.keyCode === 13 && e.ctrlKey) { 82 | thiz.val(thiz.val() + "\n"); 83 | scrollToBottom(thiz); 84 | e.preventDefault() 85 | } else if (e.keyCode === 13) { 86 | performAction(e); 87 | e.preventDefault() 88 | } 89 | }); 90 | btns.off("click").on("click", performAction) 91 | }; 92 | function htmlEncode(value) { 93 | return $('
').text(value).html() 94 | } 95 | function htmlDecode(value) { 96 | return $('
').html(value).text() 97 | } 98 | function scrollToBottom(obj) { 99 | obj[0].scrollTop = obj[0].scrollHeight 100 | } -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

asd欢迎来到WebSocket聊天室

8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 | 聊天 18 | 视频 19 | 语音 20 | 文件 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 发送 31 | 关闭 32 | 33 |
34 |
35 |
36 |
37 |
38 | 60 |
61 | 66 | 67 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/microphoneTest1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 浏览器捕捉麦克风功能测试1 8 | 20 | 21 | 22 |
23 |

浏览器捕捉麦克风功能测试1

24 |
25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 40 | 41 | 42 |
播放麦克风采集的原始音频流 30 | 31 | 32 |
录音+播放的形式来模拟连续的声音 37 | 38 | 39 |
43 |
44 | 45 | 46 | 110 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/microphoneTest2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 浏览器捕捉麦克风功能测试2 8 | 9 | 10 |
11 |

浏览器捕捉麦克风功能测试2

12 |
13 |
14 | 15 | 16 |
17 |

点击“开始录制”按钮开始录音

18 |
19 |
20 | 21 | 22 | 54 | -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/Toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/Toolbar.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/btn_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/btn_close.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/btn_close_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/btn_close_down.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/btn_close_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/btn_close_hover.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/canvaspost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/canvaspost.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/headpic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/headpic.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/mod_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/mod_chat.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/mod_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/mod_file.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/mod_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/mod_video.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/mod_voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/mod_voice.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/photo_loading.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/photo_loading.jpg -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/sprite_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/sprite_main.png -------------------------------------------------------------------------------- /websocket-chat/src/main/webapp/ws/pic/websocket/videopost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anyesu/websocket/7693dc0ae6c5b1ac6b7dcac7081288f624ab56f6/websocket-chat/src/main/webapp/ws/pic/websocket/videopost.png -------------------------------------------------------------------------------- /websocket-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | websocket-core 6 | jar 7 | 1.0-SNAPSHOT 8 | websocket-core 9 | 10 | 11 | indi.anyesu.websocket 12 | websocket-parent 13 | 1.0-SNAPSHOT 14 | ../websocket-parent/pom.xml 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | javax.websocket 23 | javax.websocket-api 24 | 1.1 25 | provided 26 | 27 | 28 | 29 | com.alibaba 30 | fastjson 31 | 1.2.4 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /websocket-core/src/main/java/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 | 23 | private Session session; 24 | private String userName; 25 | 26 | /** 27 | * 当前所有连接 28 | * 29 | * @return 当前所有连接 30 | */ 31 | abstract List getConnections(); 32 | 33 | /** 34 | * 返回连接类型 35 | * 36 | * @return 连接类型 37 | */ 38 | abstract String getConnectType(); 39 | 40 | /** 41 | * websocket连接建立后触发 42 | * 43 | * @param session 当前连接通道 44 | * @param config 配置 45 | */ 46 | protected void onOpen(Session session, EndpointConfig config) { 47 | getConnections().add(this); 48 | broadcast2All(new Message(getUserName(), MsgConstant.OPEN, getUsers()).toString()); 49 | System.out.println(getConnectType() + ": " + getUserName() + "加入了,当前总人数:" + getConnections().size()); 50 | } 51 | 52 | /** 53 | * websocket连接断开后触发 54 | */ 55 | protected void onClose() { 56 | getConnections().remove(this); 57 | broadcast2Others(new Message(getUserName(), MsgConstant.CLOSE, getUsers()).toString()); 58 | System.out.println(getConnectType() + ": " + getUserName() + "退出了,当前总人数:" + getConnections().size()); 59 | } 60 | 61 | /** 62 | * 接受客户端发送的字符串 63 | * 64 | * @param message 字符串消息 65 | */ 66 | protected void onMessage(String message) { 67 | Message msg = JSONObject.parseObject(message, Message.class); 68 | msg.setHost(getUserName()); 69 | broadcast2Others(msg.toString()); 70 | } 71 | 72 | /** 73 | * 接收客户端发送的字节流 74 | * 75 | * @param message 二进制消息 76 | */ 77 | protected void onMessage(ByteBuffer message) { 78 | broadcast2Others(message); 79 | } 80 | 81 | /** 82 | * 发生错误 83 | */ 84 | protected void onError(Throwable t) { 85 | } 86 | 87 | /** 88 | * 广播给所有用户 89 | * 90 | * @param msg 消息 91 | */ 92 | protected void broadcast2All(T msg) { 93 | for (AbstractWsController client : getConnections()) { 94 | client.call(msg); 95 | } 96 | } 97 | 98 | /** 99 | * 发送给指定的用户 100 | * 101 | * @param msg 消息 102 | */ 103 | protected void broadcast2Special(T msg, String[] dests) { 104 | for (AbstractWsController client : getConnections()) { 105 | if (StringUtil.contains(dests, client.getUserName())) { 106 | client.call(msg); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * 广播给除了自己外的用户 113 | * 114 | * @param msg 消息 115 | */ 116 | protected void broadcast2Others(T msg) { 117 | for (AbstractWsController client : getConnections()) { 118 | if (!client.getUserName().equals(this.getUserName())) { 119 | client.call(msg); 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * 异步方式向客户端发送字符串 126 | * 127 | * @param msg 参数类型为String或ByteBuffer 128 | */ 129 | protected void callAsync(T msg) { 130 | Async remote = this.getSession().getAsyncRemote(); 131 | if (msg instanceof String) { 132 | remote.sendText((String) msg); 133 | } else if (msg instanceof ByteBuffer) { 134 | remote.sendBinary((ByteBuffer) msg); 135 | } 136 | } 137 | 138 | /** 139 | * 同步方式向客户端发送字符串 140 | * 141 | * @param msg 参数类型为String或ByteBuffer 142 | */ 143 | protected void call(T msg) { 144 | try { 145 | synchronized (this) { 146 | Basic remote = this.getSession().getBasicRemote(); 147 | if (msg instanceof String) { 148 | remote.sendText((String) msg); 149 | } else if (msg instanceof ByteBuffer) { 150 | remote.sendBinary((ByteBuffer) msg); 151 | } 152 | 153 | } 154 | } catch (IOException e) { 155 | try { 156 | this.getSession().close(); 157 | } catch (IOException ignored) { 158 | } 159 | onClose(); 160 | } 161 | } 162 | 163 | protected void setSession(Session session) { 164 | this.session = session; 165 | } 166 | 167 | protected Session getSession() { 168 | return this.session; 169 | } 170 | 171 | protected String getUserName() { 172 | return userName; 173 | } 174 | 175 | protected void setUserName(String userName) { 176 | this.userName = userName; 177 | } 178 | 179 | protected String[] getUsers() { 180 | int i = 0; 181 | String[] destArrary = new String[getConnections().size()]; 182 | for (AbstractWsController client : getConnections()) { 183 | destArrary[i++] = client.getUserName(); 184 | } 185 | return destArrary; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /websocket-core/src/main/java/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 (时间戳 + 服务器id + 2位数字序号) 8 | * 9 | * @author anyesu 10 | */ 11 | public class IdGenerator { 12 | 13 | private static int SERVER_ID = 0; 14 | private static final long LIMIT = 10; 15 | private static final Lock LOCK = new ReentrantLock(); 16 | private static long LastTime = System.currentTimeMillis(); 17 | private static int COUNT = 0; 18 | 19 | public static String getNextId() { 20 | LOCK.lock(); 21 | try { 22 | while (true) { 23 | long now = System.currentTimeMillis(); 24 | if (now == LastTime) { 25 | if (++COUNT == LIMIT) { 26 | try { 27 | Thread.currentThread(); 28 | Thread.sleep(1); 29 | } catch (InterruptedException ignored) { 30 | } 31 | continue; 32 | } 33 | } else { 34 | LastTime = now; 35 | COUNT = 0; 36 | } 37 | break; 38 | } 39 | } finally { 40 | LOCK.unlock(); 41 | } 42 | 43 | return String.format("%d%d%02d", LastTime, SERVER_ID, COUNT); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /websocket-core/src/main/java/indi/anyesu/model/Message.java: -------------------------------------------------------------------------------- 1 | package indi.anyesu.model; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | /** 6 | * @author anyesu 7 | */ 8 | public class Message { 9 | 10 | /** 11 | * 消息类型 12 | */ 13 | private int type; 14 | 15 | /** 16 | * 消息主题 17 | */ 18 | private String msg; 19 | 20 | /** 21 | * 发送者 22 | */ 23 | private String host; 24 | 25 | /** 26 | * 接受者 27 | */ 28 | private String[] dests; 29 | 30 | /** 31 | * 聊天室信息 32 | */ 33 | private RoomInfo roomInfo; 34 | 35 | public class MsgConstant { 36 | 37 | /** 38 | * 新连接 39 | */ 40 | public final static int OPEN = 1; 41 | 42 | /** 43 | * 连接断开 44 | */ 45 | public final static int CLOSE = 2; 46 | 47 | /** 48 | * 发送给所有人 49 | */ 50 | public final static int MSG_TO_ALL = 3; 51 | 52 | /** 53 | * 发送给所有人 54 | */ 55 | public final static int MSG_TO_POINTS = 4; 56 | 57 | /** 58 | * 需要登录 59 | */ 60 | public final static int REQUIRE_LOGIN = 5; 61 | 62 | /** 63 | * 设置用户名 64 | */ 65 | public final static int SET_NAME = 6; 66 | } 67 | 68 | public static class RoomInfo { 69 | 70 | /** 71 | * 聊天室名称 72 | */ 73 | private String name; 74 | 75 | /** 76 | * 创建人 77 | */ 78 | private String creator; 79 | 80 | /** 81 | * 创建时间 82 | */ 83 | private String createTime; 84 | 85 | public RoomInfo(String creator, String createTime) { 86 | this.creator = creator; 87 | this.createTime = createTime; 88 | } 89 | 90 | public RoomInfo(String name) { 91 | this.name = name; 92 | } 93 | 94 | public String getName() { 95 | return name; 96 | } 97 | 98 | public void setName(String name) { 99 | this.name = name; 100 | } 101 | 102 | public String getCreator() { 103 | return creator; 104 | } 105 | 106 | public void setCreator(String creator) { 107 | this.creator = creator; 108 | } 109 | 110 | public String getCreateTime() { 111 | return createTime; 112 | } 113 | 114 | public void setCreateTime(String createTime) { 115 | this.createTime = createTime; 116 | } 117 | } 118 | 119 | public Message() { 120 | setType(MsgConstant.MSG_TO_ALL); 121 | } 122 | 123 | public Message(String host, int type) { 124 | setHost(host); 125 | setType(type); 126 | } 127 | 128 | public Message(String host, int type, String msg) { 129 | this(host, type); 130 | setMsg(msg); 131 | } 132 | 133 | public Message(String host, int type, String[] dests) { 134 | this(host, type); 135 | setDests(dests); 136 | } 137 | 138 | @Override 139 | public String toString() { 140 | /* 141 | 序列化成json串 142 | */ 143 | return JSONObject.toJSONString(this); 144 | } 145 | 146 | public int getType() { 147 | return type; 148 | } 149 | 150 | public void setType(int type) { 151 | this.type = type; 152 | } 153 | 154 | public String getMsg() { 155 | return msg; 156 | } 157 | 158 | public void setMsg(String msg) { 159 | this.msg = msg; 160 | } 161 | 162 | public String getHost() { 163 | return host; 164 | } 165 | 166 | public void setHost(String host) { 167 | this.host = host; 168 | } 169 | 170 | public String[] getDests() { 171 | return dests; 172 | } 173 | 174 | public void setDests(String[] dests) { 175 | this.dests = dests; 176 | } 177 | 178 | public RoomInfo getRoomInfo() { 179 | return roomInfo; 180 | } 181 | 182 | public void setRoomInfo(RoomInfo roomInfo) { 183 | this.roomInfo = roomInfo; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /websocket-core/src/main/java/indi/anyesu/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package indi.anyesu.util; 2 | 3 | import java.text.DecimalFormat; 4 | 5 | public class StringUtil { 6 | 7 | public static String obj2String(Object obj, String defVal) { 8 | if (obj instanceof String) { 9 | return (String) obj; 10 | } 11 | return defVal; 12 | } 13 | 14 | /** 15 | * @param s1 16 | * @param s2 17 | * @param specialStr 18 | * @return 1.如果s2为null,返回false 2、如果s2等于specialStr,直接返回true 19 | * 3、如果s1等于null,返回false 4、如果s1包含s2,返回true 5、返回false 20 | */ 21 | public static boolean contain(String s1, String s2, String specialStr) { 22 | if (null == s2 || null == s1) { 23 | return false; 24 | } 25 | return s2.equals(specialStr) || s1.contains(s2); 26 | } 27 | 28 | public static String getString(Object str) { 29 | if (str == null) { 30 | str = ""; 31 | } 32 | return str.toString(); 33 | } 34 | 35 | public static String substring(String str, int len, String subfix) { 36 | if (null == str) { 37 | str = ""; 38 | } 39 | if (str.length() > len) { 40 | if (null == subfix) { 41 | subfix = ""; 42 | } 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 | * @param parttern 要求格式结果,例如:0.00 61 | * @return 62 | */ 63 | public static String formatNum(double d, String parttern) { 64 | DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); 65 | df.applyPattern(parttern); 66 | return df.format(d); 67 | } 68 | 69 | /** 70 | * 判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成 71 | * 72 | * @param str 73 | * @return 74 | */ 75 | public static boolean isNotBlank(String str) { 76 | int length; 77 | if (str == null) { 78 | return false; 79 | } 80 | 81 | if ((length = str.length()) == 0) { 82 | return false; 83 | } 84 | for (int i = 0; i < length; ++i) { 85 | if (Character.isWhitespace(str.charAt(i))) { 86 | return false; 87 | } 88 | } 89 | return true; 90 | } 91 | 92 | /** 93 | * 判断某字符串是否为空或长度为0或由空白符(whitespace) 构成 94 | * 95 | * @param str 96 | * @return 97 | */ 98 | public static boolean isBlank(String str) { 99 | int length; 100 | if (str == null) { 101 | return true; 102 | } 103 | 104 | if ((length = str.length()) == 0) { 105 | return true; 106 | } 107 | for (int i = 0; i < length; ++i) { 108 | if (Character.isWhitespace(str.charAt(i))) { 109 | return true; 110 | } 111 | } 112 | return false; 113 | } 114 | 115 | public static boolean isEmpty(String str) { 116 | if (null == str || str.length() == 0) { 117 | return true; 118 | } 119 | return false; 120 | } 121 | 122 | public static boolean isNotEmpty(String str) { 123 | if (null == str || str.length() == 0) { 124 | return false; 125 | } 126 | return true; 127 | } 128 | 129 | /** 130 | * html转文本 131 | * 132 | * @param htm 133 | * @return 134 | */ 135 | public static String htm2txt(String htm) { 136 | if (StringUtil.isBlank(htm)) { 137 | return htm; 138 | } 139 | return htm.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll(" ", " ").replaceAll("
", "\n").replaceAll("'", "\'"); 140 | } 141 | 142 | /** 143 | * html代码转义 144 | * 145 | * @param txt 146 | * @return 147 | */ 148 | public static String txt2htm(String txt) { 149 | if (StringUtil.isBlank(txt)) { 150 | return txt; 151 | } 152 | return txt.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll(" ", " ").replaceAll("\n", "
").replaceAll("\'", "'"); 153 | } 154 | 155 | public static boolean contains(String[] strs, String str) { 156 | if (isEmpty(str) || strs.length == 0) { 157 | return false; 158 | } 159 | for (String s : strs) { 160 | if (s.equals(str)) { 161 | return true; 162 | } 163 | } 164 | return false; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /websocket-parent/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | indi.anyesu.websocket 7 | websocket-parent 8 | pom 9 | 1.0-SNAPSHOT 10 | websocket-parent 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 1.7 16 | 1.7 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 2.5 26 | 27 | ${maven.compiler.source} 28 | ${maven.compiler.target} 29 | UTF-8 30 | 31 | 32 | 33 | org.apache.maven.plugins 34 | maven-jar-plugin 35 | 2.5 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-resources-plugin 40 | 2.5 41 | 42 | UTF-8 43 | 44 | 45 | 46 | 47 | compile 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /websocket-samples/Nodejs-Websocket/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | MAINTAINER anyesu 3 | 4 | # 拷贝项目 5 | COPY . /usr/anyesu/node 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 | # 安装ws模块 15 | npm install ws@1.1.0 express -g 16 | 17 | # 设置环境变量 18 | ENV NODE_PATH /usr/local/lib/node_modules 19 | 20 | # 启动命令(前台程序) 21 | CMD ["node", "/usr/anyesu/node/server.js"] -------------------------------------------------------------------------------- /websocket-samples/Nodejs-Websocket/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 | 73 | 不支持canvas标签 74 | 75 | 76 |
77 | 78 | 79 | 80 |
81 | 82 | -------------------------------------------------------------------------------- /websocket-samples/Nodejs-Websocket/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 video{width: 120px; height: 90px; 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/Nodejs-Websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |

asd欢迎来到WebSocket聊天室

21 |
22 |
23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 | 聊天 31 | 视频 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 | 发送 44 | 关闭 45 | 46 |
47 |
48 |
49 |
50 |
51 | 73 |
74 |
75 | 78 |
79 | 84 | 85 | -------------------------------------------------------------------------------- /websocket-samples/Nodejs-Websocket/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/Nodejs-Websocket/js/app.js: -------------------------------------------------------------------------------- 1 | $(document.body).ready(function(e) { 2 | NO_SOURCE.src = "/pic/websocket/canvaspost.png"; 3 | textClient.initialize(); 4 | Console.setMode(Console.ChatMode); 5 | 6 | $(".pageTop .func a").on("click", function() { 7 | if ($(this).attr("class").indexOf("click") > -1) return; 8 | $(".pageTop .func a.click").removeClass("click"); 9 | $(this).addClass("click"); 10 | Console.setMode($(this).index()) 11 | }); 12 | 13 | $(".mwd>:not(.pageLeft)").each(function() { 14 | this.onselectstart = function() { 15 | return false 16 | } 17 | }); 18 | 19 | var ScrollConfig = { 20 | cursorcolor: "#ffdb51", 21 | cursoropacitymax: 0.5, 22 | cursorwidth: "5PX", 23 | cursorborder: "0px solid #000", 24 | grabcursorenabled: false, 25 | preservenativescrolling: false, 26 | nativeparentscrolling: true, 27 | enablescrollonselection: true 28 | }; 29 | $("[MyScroll]").each(function() { 30 | $(this).niceScroll(ScrollConfig) 31 | }); 32 | $(window).resize(function() { 33 | if (Console.fullScreen) Console.resize() 34 | }); 35 | $(".pageTop .toolbar .min").bind("click", Console.toggleMin); 36 | $(".pageTop .toolbar .max").bind("click", Console.toggleFullScreen); 37 | $(".pageTop .toolbar .close").bind("click", Console.close); 38 | $("#min-max .back").bind("click", Console.toggleMin); 39 | $("#min-max .max").bind("click", Console.minToMax); 40 | $("#min-max .close").bind("click", Console.close); 41 | $(".mwd .pageLeft .edit .buttons .close").bind("click", Console.close) 42 | }); 43 | 44 | function CloseWindow() { 45 | if (typeof(WeixinJSBridge) != "undefined") { 46 | WeixinJSBridge.call('closeWindow') 47 | } else { 48 | var opened = window.open('about:blank', '_self'); 49 | opened.opener = null; 50 | opened.close() 51 | } 52 | } 53 | function StringBuffer(str) { 54 | this.strArray = new Array(); 55 | this.strArray.push(str); 56 | this.append = function(appendStr) { 57 | this.strArray.push(appendStr) 58 | }; 59 | this.toString = function() { 60 | return this.strArray.join("") 61 | } 62 | } 63 | $.fn.ctrlEnter = function(btns, fn) { 64 | var thiz = $(this); 65 | btns = $(btns); 66 | 67 | function performAction(e) { 68 | fn.call(thiz, e) 69 | }; 70 | thiz.unbind(); 71 | thiz.bind("keydown", function(e) { 72 | if (e.keyCode === 13 && e.ctrlKey) { 73 | thiz.val(thiz.val() + "\n"); 74 | scrollToBottom(thiz); 75 | e.preventDefault() 76 | } else if (e.keyCode === 13) { 77 | performAction(e); 78 | e.preventDefault() 79 | } 80 | }); 81 | btns.bind("click", performAction) 82 | } 83 | function htmlEncode(value) { 84 | return $('
').text(value).html() 85 | } 86 | function htmlDecode(value) { 87 | return $('
').html(value).text() 88 | } 89 | function scrollToBottom(obj) { 90 | obj[0].scrollTop = obj[0].scrollHeight 91 | } -------------------------------------------------------------------------------- /websocket-samples/Nodejs-Websocket/js/chat.js: -------------------------------------------------------------------------------- 1 | var MODE_TEXT = 0, 2 | MODE_VIDEO = 1, 3 | MODE_AUDIO = 2, 4 | NO_SOURCE = new Image(), 5 | textClient = new WSClient({ 6 | host: "ws://" + window.location.hostname + ":3002/websocket/chat", 7 | type: MODE_TEXT, 8 | onopen: function() { 9 | $(".mainedit").ctrlEnter("#submit", function(event) { 10 | var message = { 11 | type: 3, 12 | msg: $(Console.Win).find(".mainedit").val() 13 | }; 14 | if (message.msg.trim() != '') { 15 | textClient.sendString(JSON.stringify(message)); 16 | $(Console.Win).find(".mainedit").val('') 17 | } else Console.log("不能发送空消息", false, 3000) 18 | }); 19 | Console.log('WebSocket已连接.') 20 | }, 21 | onclose: function() { 22 | $(".mainedit").ctrlEnter("#submit", function(event) { 23 | Console.log('WebSocket已断开.请刷新页面以重新连接', true) 24 | }); 25 | Console.log('Info: WebSocket已断开.', true) 26 | }, 27 | wsonopen: function(msg) { 28 | textClient.initUserList(msg.dests); 29 | if (textClient.isMe(msg.host)) { 30 | textClient.online = true; 31 | msg = "您已加入聊天室" 32 | } else { 33 | msg = msg.host + "加入了聊天室" 34 | } 35 | Console.log(msg) 36 | }, 37 | wsonclose: function(msg) { 38 | if (textClient.isMe(msg.host)) { 39 | textClient.online = false; 40 | textClient.initUserList(null); 41 | msg = "您已退出聊天室" 42 | } else { 43 | textClient.initUserList(msg.dests); 44 | msg = msg.host + "退出了聊天室" 45 | } 46 | Console.log(msg, true) 47 | }, 48 | wsonmessage: function(msg) { 49 | msg.msg = msg.msg.replace(/\n/g, "
"); 50 | if (textClient.isMe(msg.host)) textClient.addsrcMsg(msg); 51 | else textClient.adddestMsg(msg) 52 | }, 53 | wssetname: function(msg) { 54 | textClient.setRoomInfo(msg.roomInfo); 55 | $("#user").text(textClient.option.userName); 56 | } 57 | }); 58 | textClient.addsrcMsg = function(msg) { 59 | var console = Console.Win + " #console", 60 | obj = '


' + new Date().toLocaleString() + '

'; 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(''); 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 | 73 | 不支持canvas标签 74 | 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 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | 聊天 30 | 视频 31 | 语音 32 | 文件 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 | 发送 43 | 关闭 44 | 45 |
46 |
47 |
48 |
49 |
50 | 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 | --------------------------------------------------------------------------------