├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── org │ └── red5 │ └── demos │ └── chat │ ├── Application.java │ ├── Router.java │ └── WebSocketChatDataListener.java ├── resources └── logback-chat.xml └── webapp ├── WEB-INF ├── red5-web.properties ├── red5-web.xml └── web.xml ├── flash2ws.html ├── framework_4.6.0.23201.swz ├── index.html ├── playerProductInstall.swf ├── simpleSOChat.html ├── simpleSOChat.swf ├── spark_4.6.0.23201.swz ├── swfobject.js └── textLayout_2.0.0.232.swz /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .project 4 | .classpath 5 | .DS_Store 6 | 7 | *.class 8 | *.zip 9 | *.tar.gz 10 | *~ 11 | 12 | Thumbs.db 13 | build.sh 14 | log/ 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chat application 2 | ================ 3 | 4 | Red5 WebSocket chat application example. 5 | 6 | The example index.html defaults to using a WebSocket connection to localhost on port 5080. 7 | 8 | # Add the WebSocket filter servlet to webapps that require WebSocket support 9 | 10 | ```xml 11 | 12 | WebSocketFilter 13 | org.red5.net.websocket.server.WsFilter 14 | true 15 | 16 | 17 | WebSocketFilter 18 | /* 19 | REQUEST 20 | FORWARD 21 | 22 | ``` 23 | 24 | Build the application from the command line with 25 | 26 | ```sh 27 | mvn package 28 | ``` 29 | 30 | Deploy your application by copying the war file into your red5/webapps directory. If the war file does not deploy withing a few minutes, this may indicate the war deployer bean is not created or running; a work-around is to expand the war contents into the webapps directory manually and restart Red5. 31 | 32 | After deploy is complete, go to http://localhost:5080/chat/ in your browser (open two tabs if you want to chat back and forth on the same computer). 33 | 34 | Pre-compiled WAR 35 | ---------------- 36 | You can find [compiled artifacts via Maven](http://mvnrepository.com/artifact/org.red5.demos/chat) 37 | 38 | [Direct Download](https://oss.sonatype.org/content/repositories/releases/org/red5/demos/chat/2.0.0/chat-2.0.0.war) 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | org.red5 5 | red5-parent 6 | 1.3.33 7 | 8 | 4.0.0 9 | org.red5.demos 10 | chat 11 | war 12 | chat 13 | chat application 14 | 15 | Red5 16 | https://github.com/Red5 17 | 18 | 19 | 20 | Apache 2 21 | http://www.apache.org/licenses/LICENSE-2.0.txt 22 | repo 23 | A business-friendly OSS license 24 | 25 | 26 | 27 | 28 | red5-interest 29 | https://groups.google.com/forum/?fromgroups#!forum/red5interest 30 | 31 | 32 | 33 | https://github.com/Red5/red5-websocket-chat.git 34 | scm:git:git@github.com:Red5/red5-websocket-chat.git 35 | scm:git:git@github.com:Red5/red5-websocket-chat.git 36 | 37 | 38 | 39 | Paul Gregoire 40 | mondain@gmail.com 41 | 42 | 43 | 44 | UTF-8 45 | 46 | false 47 | 48 | true 49 | 50 | false 51 | 1.11 52 | 1.11 53 | 11 54 | 1.3.33 55 | 2.9.0 56 | 57 | 58 | package 59 | 60 | 61 | maven-source-plugin 62 | 63 | 64 | maven-javadoc-plugin 65 | 66 | 67 | maven-dependency-plugin 68 | 69 | 70 | maven-war-plugin 71 | 3.3.1 72 | 73 | *.html,*.swf,*.js,*.swz,WEB-INF/*.properties,WEB-INF/*.xml,WEB-INF/classes/** 74 | WEB-INF/lib/*.jar 75 | 76 | 77 | 78 | org.apache.felix 79 | maven-bundle-plugin 80 | true 81 | 82 | 83 | 84 | 85 | 86 | org.red5 87 | red5-server 88 | ${red5.version} 89 | jar 90 | compile 91 | 92 | 93 | org.apache.mina 94 | mina-core 95 | ${mina.version} 96 | bundle 97 | 98 | 99 | org.springframework 100 | spring-beans 101 | ${spring.version} 102 | 103 | 104 | commons-logging 105 | commons-logging 106 | 107 | 108 | 109 | 110 | org.springframework 111 | spring-context 112 | 113 | 114 | commons-logging 115 | commons-logging 116 | 117 | 118 | 119 | 120 | org.springframework 121 | spring-context-support 122 | 123 | 124 | commons-logging 125 | commons-logging 126 | 127 | 128 | 129 | 130 | com.google.code.gson 131 | gson 132 | ${gson.version} 133 | provided 134 | 135 | 136 | * 137 | * 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/main/java/org/red5/demos/chat/Application.java: -------------------------------------------------------------------------------- 1 | package org.red5.demos.chat; 2 | 3 | import org.red5.server.adapter.MultiThreadedApplicationAdapter; 4 | import org.red5.server.api.scope.IScope; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | 11 | /** 12 | * Main application entry point for the chat application. 13 | * 14 | * @author Paul Gregoire 15 | */ 16 | public class Application extends MultiThreadedApplicationAdapter implements ApplicationContextAware { 17 | 18 | // Updated SLF4J logger makes it difficult to use the logger with a context, it is currently buggy in Red5 19 | //private static Logger log = Red5LoggerFactory.getLogger(Application.class, "chat"); 20 | private static Logger log = LoggerFactory.getLogger(Application.class); // use this for now instead 21 | 22 | @SuppressWarnings("unused") 23 | private ApplicationContext applicationContext; 24 | 25 | @Override 26 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 27 | this.applicationContext = applicationContext; 28 | } 29 | 30 | @Override 31 | public boolean appStart(IScope scope) { 32 | log.info("Chat starting"); 33 | return super.appStart(scope); 34 | } 35 | 36 | @Override 37 | public void appStop(IScope scope) { 38 | log.info("Chat stopping"); 39 | super.appStop(scope); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/red5/demos/chat/Router.java: -------------------------------------------------------------------------------- 1 | package org.red5.demos.chat; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import org.red5.server.adapter.ApplicationLifecycle; 7 | import org.red5.server.api.IAttributeStore; 8 | import org.red5.server.api.Red5; 9 | import org.red5.server.api.scope.IScope; 10 | import org.red5.server.api.so.ISharedObject; 11 | import org.red5.server.api.so.ISharedObjectBase; 12 | import org.red5.server.api.so.ISharedObjectListener; 13 | import org.red5.server.util.ScopeUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Router for chat messages between WebSockets and an associated SharedObject. 19 | * 20 | * @author Paul Gregoire 21 | */ 22 | public class Router { 23 | 24 | private static Logger log = LoggerFactory.getLogger(Router.class); 25 | 26 | private Application app; 27 | 28 | private WebSocketChatDataListener wsListener; 29 | 30 | /** 31 | * Routes a message on a given path to the associated shared object. 32 | * 33 | * @param path shared object path / name 34 | * @param message string 35 | */ 36 | public void route(String path, String message) { 37 | log.debug("Route to Shared Object: {} with {}", path, message); 38 | // get the shared object 39 | ISharedObject so = getSharedObject(path); 40 | if (so != null) { 41 | // set the message attribute 42 | so.setAttribute("message", message); 43 | } else { 44 | log.warn("Shared object was not available for path: {}", path); 45 | } 46 | } 47 | 48 | /** 49 | * Routes a message on a given scope to the associated websocket connections. 50 | * 51 | * @param scope application or room scope 52 | * @param message string 53 | */ 54 | public void route(IScope scope, String message) { 55 | // scope.path = /default scope.name = chat 56 | String path = scope.getContextPath(); 57 | log.debug("Route to WebSocket: {} with {}", path, message); 58 | if (getSharedObject(path) == null) { 59 | log.warn("Shared object for path: {} did not exist", path); 60 | } 61 | if (wsListener != null) { 62 | wsListener.sendToAll(scope.getContextPath(), message); 63 | } 64 | } 65 | 66 | /** 67 | * Get the chat shared object for a given path. 68 | * 69 | * @param path shared object path / name 70 | * @return the shared object for the path or null if its not available 71 | */ 72 | private ISharedObject getSharedObject(String path) { 73 | // get the application level scope 74 | IScope appScope = app.getScope(); 75 | // resolve the path given to an existing scope 76 | IScope scope = ScopeUtils.resolveScope(appScope, path); 77 | if (scope == null) { 78 | // attempt to create the missing scope for the given path 79 | if (!appScope.createChildScope(path)) { 80 | log.warn("Scope creation failed for {}", path); 81 | return null; 82 | } 83 | scope = ScopeUtils.resolveScope(appScope, path); 84 | } 85 | // get the shared object 86 | ISharedObject so = app.getSharedObject(scope, "chat"); 87 | if (so == null) { 88 | if (!app.createSharedObject(scope, "chat", false)) { 89 | log.warn("Chat SO creation failed"); 90 | return null; 91 | } 92 | // get the newly created shared object 93 | so = app.getSharedObject(scope, "chat"); 94 | } 95 | // ensure the so is acquired and our listener has been added 96 | if (!so.isAcquired()) { 97 | // acquire the so to prevent it being removed unexpectedly 98 | so.acquire(); // TODO in a "real" world implementation, this would need to be paired with a call to release when the so is no longer needed 99 | // add a listener for detecting sync on the so 100 | so.addSharedObjectListener(new SOListener(this, scope, path)); 101 | } 102 | return so; 103 | } 104 | 105 | public void setApp(Application app) { 106 | this.app = app; 107 | this.app.addListener(new ApplicationLifecycle() { 108 | 109 | @Override 110 | public void appStop(IScope scope) { 111 | if (wsListener != null) { 112 | wsListener.stop(); 113 | } 114 | } 115 | 116 | }); 117 | } 118 | 119 | public void setWsListener(WebSocketChatDataListener wsListener) { 120 | this.wsListener = wsListener; 121 | } 122 | 123 | private final class SOListener implements ISharedObjectListener { 124 | 125 | private final Router router; 126 | 127 | private final IScope scope; 128 | 129 | private final String path; 130 | 131 | SOListener(Router router, IScope scope, String path) { 132 | log.debug("ctor - path: {} scope: {}", path, scope); 133 | this.router = router; 134 | this.scope = scope; 135 | this.path = path; 136 | } 137 | 138 | public void onSharedObjectClear(ISharedObjectBase so) { 139 | log.debug("onSharedObjectClear path: {}", path); 140 | } 141 | 142 | public void onSharedObjectConnect(ISharedObjectBase so) { 143 | log.debug("onSharedObjectConnect path: {}", path); 144 | } 145 | 146 | public void onSharedObjectDelete(ISharedObjectBase so, String key) { 147 | log.debug("onSharedObjectDelete path: {} key: {}", path, key); 148 | } 149 | 150 | public void onSharedObjectDisconnect(ISharedObjectBase so) { 151 | log.debug("onSharedObjectDisconnect path: {}", path); 152 | } 153 | 154 | public void onSharedObjectSend(ISharedObjectBase so, String method, List attributes) { 155 | log.debug("onSharedObjectSend path: {} - method: {} {}", path, method, attributes); 156 | } 157 | 158 | public void onSharedObjectUpdate(ISharedObjectBase so, IAttributeStore attributes) { 159 | log.debug("onSharedObjectUpdate path: {} - {}", path, attributes); 160 | } 161 | 162 | public void onSharedObjectUpdate(ISharedObjectBase so, Map attributes) { 163 | log.debug("onSharedObjectUpdate path: {} - {}", path, attributes); 164 | } 165 | 166 | public void onSharedObjectUpdate(ISharedObjectBase so, String key, Object value) { 167 | log.debug("onSharedObjectUpdate path: {} - {} = {}", path, key, value); 168 | // route to the websockets if we have an RTMP connection as the originator, otherwise websockets will get duplicate messages 169 | if (Red5.getConnectionLocal() != null) { 170 | router.route(scope, value.toString()); 171 | } 172 | } 173 | 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/org/red5/demos/chat/WebSocketChatDataListener.java: -------------------------------------------------------------------------------- 1 | package org.red5.demos.chat; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.Set; 7 | 8 | import org.red5.net.websocket.WSConstants; 9 | import org.red5.net.websocket.WebSocketConnection; 10 | import org.red5.net.websocket.listener.WebSocketDataListener; 11 | import org.red5.net.websocket.model.WSMessage; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import com.google.gson.JsonObject; 16 | import com.google.gson.JsonParser; 17 | 18 | /** 19 | * Handler / router for chat data. 20 | * 21 | * @author Paul Gregoire 22 | */ 23 | public class WebSocketChatDataListener extends WebSocketDataListener { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(WebSocketChatDataListener.class); 26 | 27 | { 28 | setProtocol("chat"); 29 | } 30 | 31 | private Router router; 32 | 33 | private Set connections = new HashSet<>(); 34 | 35 | @Override 36 | public void onWSConnect(WebSocketConnection conn) { 37 | log.info("Connect: {}", conn); 38 | Optional> header = Optional.ofNullable(conn.getHeaders().get(WSConstants.WS_HEADER_PROTOCOL)); 39 | if (header.isPresent()) { 40 | log.debug("Protocol header exists"); 41 | String protocol = header.get().get(0); 42 | if (protocol.contains("chat")) { 43 | log.debug("Chat enabled"); 44 | conn.setProtocol(protocol); 45 | } else { 46 | log.info("Chat is not in the connections protocol list"); 47 | } 48 | } 49 | connections.add(conn); 50 | } 51 | 52 | @Override 53 | public void onWSDisconnect(WebSocketConnection conn) { 54 | log.info("Disconnect: {}", conn); 55 | connections.remove(conn); 56 | } 57 | 58 | @Override 59 | public void onWSMessage(WSMessage message) { 60 | // if its protocol doesn't match then skip the message 61 | if (!protocol.equals(message.getConnection().getProtocol())) { 62 | log.debug("Skipping message due to protocol mismatch"); 63 | return; 64 | } 65 | // get the connection path for routing 66 | String path = message.getConnection().getPath(); 67 | log.debug("WebSocket connection path: {}", path); 68 | // assume we have text 69 | String msg = new String(message.getPayload().array()).trim(); 70 | log.info("onWSMessage: {}\n{}", msg, message.getConnection()); 71 | // do a quick hacky json check 72 | if (msg.indexOf('{') != -1 && msg.indexOf(':') != -1) { 73 | log.info("JSON encoded text message"); 74 | // channelName == roomid in most cases 75 | try { 76 | JsonObject obj = JsonParser.parseString(msg).getAsJsonObject(); 77 | log.debug("Parsed - keys: {}\ncontent: {}", obj.keySet(), obj); 78 | msg = obj.toString(); 79 | // send to all websocket connections matching this connections path 80 | sendToAll(path, msg); 81 | // send to the shared object matching this connections path 82 | router.route(path, msg); 83 | } catch (Exception e) { 84 | log.warn("Exception parsing JSON", e); 85 | } 86 | } else { 87 | log.info("Standard text message"); 88 | // send to all websocket connections matching this connections path 89 | sendToAll(path, msg); 90 | // send to the shared object matching this connections path 91 | router.route(path, msg); 92 | } 93 | } 94 | 95 | /** 96 | * Send message to all connected connections. 97 | * 98 | * @param path routable path / name 99 | * @param message string 100 | */ 101 | public void sendToAll(String path, String message) { 102 | for (WebSocketConnection conn : connections) { 103 | if (path.equals(conn.getPath())) { 104 | try { 105 | conn.send(message); 106 | } catch (Exception e) { 107 | log.warn("Exception sending message", e); 108 | } 109 | } else { 110 | log.trace("Path did not match for message {} != {}", path, conn.getPath()); 111 | } 112 | } 113 | } 114 | 115 | public void setRouter(Router router) { 116 | this.router = router; 117 | this.router.setWsListener(this); 118 | } 119 | 120 | @Override 121 | public void stop() { 122 | connections.forEach(wc -> wc.close()); 123 | connections.clear(); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/resources/logback-chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | chat 4 | 5 | 6 | 7 | 8 | log/chat.log 9 | false 10 | UTF-8 11 | false 12 | true 13 | 14 | 15 | %date [%thread] %-5level %logger{35} - %msg%n 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/red5-web.properties: -------------------------------------------------------------------------------- 1 | webapp.contextPath=/chat 2 | webapp.virtualHosts=* 3 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/red5-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | /WEB-INF/red5-web.properties 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | chat 7 | 8 | webAppRootKey 9 | /chat 10 | 11 | 12 | 13 | WebSocketFilter 14 | org.red5.net.websocket.server.WsFilter 15 | false 16 | 17 | 18 | WebSocketFilter 19 | /* 20 | REQUEST 21 | FORWARD 22 | 23 | -------------------------------------------------------------------------------- /src/main/webapp/flash2ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Red5 WebSocket Chat 5 | 17 | 18 | 19 |

This demonstrates bidirectional chat between HTML and Flash

20 | 21 | 33 |
22 |
23 |
    24 |
    25 | 26 |
    27 |
    28 |
    29 |
    30 | 31 |
    32 |
    34 | 35 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/webapp/framework_4.6.0.23201.swz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Red5/red5-websocket-chat/ec7bf87e222bcc91b2f0f9f699a8bf08474cf6d9/src/main/webapp/framework_4.6.0.23201.swz -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Red5 WebSocket Chat 5 | 15 | 16 | 17 |
      18 |
      19 | 20 |
      21 | 22 | 82 | 83 | -------------------------------------------------------------------------------- /src/main/webapp/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Red5/red5-websocket-chat/ec7bf87e222bcc91b2f0f9f699a8bf08474cf6d9/src/main/webapp/playerProductInstall.swf -------------------------------------------------------------------------------- /src/main/webapp/simpleSOChat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 39 | 40 | 41 |
      42 |

      43 | To view this page ensure that Adobe Flash Player version 44 | 11.1.0 or greater is installed. 45 |

      46 |
      47 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/webapp/simpleSOChat.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Red5/red5-websocket-chat/ec7bf87e222bcc91b2f0f9f699a8bf08474cf6d9/src/main/webapp/simpleSOChat.swf -------------------------------------------------------------------------------- /src/main/webapp/spark_4.6.0.23201.swz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Red5/red5-websocket-chat/ec7bf87e222bcc91b2f0f9f699a8bf08474cf6d9/src/main/webapp/spark_4.6.0.23201.swz -------------------------------------------------------------------------------- /src/main/webapp/swfobject.js: -------------------------------------------------------------------------------- 1 | /*! SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | 5 | var swfobject = function() { 6 | 7 | var UNDEF = "undefined", 8 | OBJECT = "object", 9 | SHOCKWAVE_FLASH = "Shockwave Flash", 10 | SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", 11 | FLASH_MIME_TYPE = "application/x-shockwave-flash", 12 | EXPRESS_INSTALL_ID = "SWFObjectExprInst", 13 | ON_READY_STATE_CHANGE = "onreadystatechange", 14 | 15 | win = window, 16 | doc = document, 17 | nav = navigator, 18 | 19 | plugin = false, 20 | domLoadFnArr = [main], 21 | regObjArr = [], 22 | objIdArr = [], 23 | listenersArr = [], 24 | storedAltContent, 25 | storedAltContentId, 26 | storedCallbackFn, 27 | storedCallbackObj, 28 | isDomLoaded = false, 29 | isExpressInstallActive = false, 30 | dynamicStylesheet, 31 | dynamicStylesheetMedia, 32 | autoHideShow = true, 33 | 34 | /* Centralized function for browser feature detection 35 | - User agent string detection is only used when no good alternative is possible 36 | - Is executed directly for optimal performance 37 | */ 38 | ua = function() { 39 | var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF, 40 | u = nav.userAgent.toLowerCase(), 41 | p = nav.platform.toLowerCase(), 42 | windows = p ? /win/.test(p) : /win/.test(u), 43 | mac = p ? /mac/.test(p) : /mac/.test(u), 44 | webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit 45 | ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html 46 | playerVersion = [0,0,0], 47 | d = null; 48 | if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { 49 | d = nav.plugins[SHOCKWAVE_FLASH].description; 50 | if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+ 51 | plugin = true; 52 | ie = false; // cascaded feature detection for Internet Explorer 53 | d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); 54 | playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); 55 | playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10); 56 | playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; 57 | } 58 | } 59 | else if (typeof win.ActiveXObject != UNDEF) { 60 | try { 61 | var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); 62 | if (a) { // a will return null when ActiveX is disabled 63 | d = a.GetVariable("$version"); 64 | if (d) { 65 | ie = true; // cascaded feature detection for Internet Explorer 66 | d = d.split(" ")[1].split(","); 67 | playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 68 | } 69 | } 70 | } 71 | catch(e) {} 72 | } 73 | return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac }; 74 | }(), 75 | 76 | /* Cross-browser onDomLoad 77 | - Will fire an event as soon as the DOM of a web page is loaded 78 | - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/ 79 | - Regular onload serves as fallback 80 | */ 81 | onDomLoad = function() { 82 | if (!ua.w3) { return; } 83 | if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically 84 | callDomLoadFunctions(); 85 | } 86 | if (!isDomLoaded) { 87 | if (typeof doc.addEventListener != UNDEF) { 88 | doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false); 89 | } 90 | if (ua.ie && ua.win) { 91 | doc.attachEvent(ON_READY_STATE_CHANGE, function() { 92 | if (doc.readyState == "complete") { 93 | doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee); 94 | callDomLoadFunctions(); 95 | } 96 | }); 97 | if (win == top) { // if not inside an iframe 98 | (function(){ 99 | if (isDomLoaded) { return; } 100 | try { 101 | doc.documentElement.doScroll("left"); 102 | } 103 | catch(e) { 104 | setTimeout(arguments.callee, 0); 105 | return; 106 | } 107 | callDomLoadFunctions(); 108 | })(); 109 | } 110 | } 111 | if (ua.wk) { 112 | (function(){ 113 | if (isDomLoaded) { return; } 114 | if (!/loaded|complete/.test(doc.readyState)) { 115 | setTimeout(arguments.callee, 0); 116 | return; 117 | } 118 | callDomLoadFunctions(); 119 | })(); 120 | } 121 | addLoadEvent(callDomLoadFunctions); 122 | } 123 | }(); 124 | 125 | function callDomLoadFunctions() { 126 | if (isDomLoaded) { return; } 127 | try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early 128 | var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span")); 129 | t.parentNode.removeChild(t); 130 | } 131 | catch (e) { return; } 132 | isDomLoaded = true; 133 | var dl = domLoadFnArr.length; 134 | for (var i = 0; i < dl; i++) { 135 | domLoadFnArr[i](); 136 | } 137 | } 138 | 139 | function addDomLoadEvent(fn) { 140 | if (isDomLoaded) { 141 | fn(); 142 | } 143 | else { 144 | domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+ 145 | } 146 | } 147 | 148 | /* Cross-browser onload 149 | - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/ 150 | - Will fire an event as soon as a web page including all of its assets are loaded 151 | */ 152 | function addLoadEvent(fn) { 153 | if (typeof win.addEventListener != UNDEF) { 154 | win.addEventListener("load", fn, false); 155 | } 156 | else if (typeof doc.addEventListener != UNDEF) { 157 | doc.addEventListener("load", fn, false); 158 | } 159 | else if (typeof win.attachEvent != UNDEF) { 160 | addListener(win, "onload", fn); 161 | } 162 | else if (typeof win.onload == "function") { 163 | var fnOld = win.onload; 164 | win.onload = function() { 165 | fnOld(); 166 | fn(); 167 | }; 168 | } 169 | else { 170 | win.onload = fn; 171 | } 172 | } 173 | 174 | /* Main function 175 | - Will preferably execute onDomLoad, otherwise onload (as a fallback) 176 | */ 177 | function main() { 178 | if (plugin) { 179 | testPlayerVersion(); 180 | } 181 | else { 182 | matchVersions(); 183 | } 184 | } 185 | 186 | /* Detect the Flash Player version for non-Internet Explorer browsers 187 | - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description: 188 | a. Both release and build numbers can be detected 189 | b. Avoid wrong descriptions by corrupt installers provided by Adobe 190 | c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports 191 | - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available 192 | */ 193 | function testPlayerVersion() { 194 | var b = doc.getElementsByTagName("body")[0]; 195 | var o = createElement(OBJECT); 196 | o.setAttribute("type", FLASH_MIME_TYPE); 197 | var t = b.appendChild(o); 198 | if (t) { 199 | var counter = 0; 200 | (function(){ 201 | if (typeof t.GetVariable != UNDEF) { 202 | var d = t.GetVariable("$version"); 203 | if (d) { 204 | d = d.split(" ")[1].split(","); 205 | ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 206 | } 207 | } 208 | else if (counter < 10) { 209 | counter++; 210 | setTimeout(arguments.callee, 10); 211 | return; 212 | } 213 | b.removeChild(o); 214 | t = null; 215 | matchVersions(); 216 | })(); 217 | } 218 | else { 219 | matchVersions(); 220 | } 221 | } 222 | 223 | /* Perform Flash Player and SWF version matching; static publishing only 224 | */ 225 | function matchVersions() { 226 | var rl = regObjArr.length; 227 | if (rl > 0) { 228 | for (var i = 0; i < rl; i++) { // for each registered object element 229 | var id = regObjArr[i].id; 230 | var cb = regObjArr[i].callbackFn; 231 | var cbObj = {success:false, id:id}; 232 | if (ua.pv[0] > 0) { 233 | var obj = getElementById(id); 234 | if (obj) { 235 | if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match! 236 | setVisibility(id, true); 237 | if (cb) { 238 | cbObj.success = true; 239 | cbObj.ref = getObjectById(id); 240 | cb(cbObj); 241 | } 242 | } 243 | else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported 244 | var att = {}; 245 | att.data = regObjArr[i].expressInstall; 246 | att.width = obj.getAttribute("width") || "0"; 247 | att.height = obj.getAttribute("height") || "0"; 248 | if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); } 249 | if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); } 250 | // parse HTML object param element's name-value pairs 251 | var par = {}; 252 | var p = obj.getElementsByTagName("param"); 253 | var pl = p.length; 254 | for (var j = 0; j < pl; j++) { 255 | if (p[j].getAttribute("name").toLowerCase() != "movie") { 256 | par[p[j].getAttribute("name")] = p[j].getAttribute("value"); 257 | } 258 | } 259 | showExpressInstall(att, par, id, cb); 260 | } 261 | else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF 262 | displayAltContent(obj); 263 | if (cb) { cb(cbObj); } 264 | } 265 | } 266 | } 267 | else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content) 268 | setVisibility(id, true); 269 | if (cb) { 270 | var o = getObjectById(id); // test whether there is an HTML object element or not 271 | if (o && typeof o.SetVariable != UNDEF) { 272 | cbObj.success = true; 273 | cbObj.ref = o; 274 | } 275 | cb(cbObj); 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | function getObjectById(objectIdStr) { 283 | var r = null; 284 | var o = getElementById(objectIdStr); 285 | if (o && o.nodeName == "OBJECT") { 286 | if (typeof o.SetVariable != UNDEF) { 287 | r = o; 288 | } 289 | else { 290 | var n = o.getElementsByTagName(OBJECT)[0]; 291 | if (n) { 292 | r = n; 293 | } 294 | } 295 | } 296 | return r; 297 | } 298 | 299 | /* Requirements for Adobe Express Install 300 | - only one instance can be active at a time 301 | - fp 6.0.65 or higher 302 | - Win/Mac OS only 303 | - no Webkit engines older than version 312 304 | */ 305 | function canExpressInstall() { 306 | return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); 307 | } 308 | 309 | /* Show the Adobe Express Install dialog 310 | - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 311 | */ 312 | function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { 313 | isExpressInstallActive = true; 314 | storedCallbackFn = callbackFn || null; 315 | storedCallbackObj = {success:false, id:replaceElemIdStr}; 316 | var obj = getElementById(replaceElemIdStr); 317 | if (obj) { 318 | if (obj.nodeName == "OBJECT") { // static publishing 319 | storedAltContent = abstractAltContent(obj); 320 | storedAltContentId = null; 321 | } 322 | else { // dynamic publishing 323 | storedAltContent = obj; 324 | storedAltContentId = replaceElemIdStr; 325 | } 326 | att.id = EXPRESS_INSTALL_ID; 327 | if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; } 328 | if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; } 329 | doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; 330 | var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", 331 | fv = "MMredirectURL=" + encodeURI(window.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; 332 | if (typeof par.flashvars != UNDEF) { 333 | par.flashvars += "&" + fv; 334 | } 335 | else { 336 | par.flashvars = fv; 337 | } 338 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, 339 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work 340 | if (ua.ie && ua.win && obj.readyState != 4) { 341 | var newObj = createElement("div"); 342 | replaceElemIdStr += "SWFObjectNew"; 343 | newObj.setAttribute("id", replaceElemIdStr); 344 | obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf 345 | obj.style.display = "none"; 346 | (function(){ 347 | if (obj.readyState == 4) { 348 | obj.parentNode.removeChild(obj); 349 | } 350 | else { 351 | setTimeout(arguments.callee, 10); 352 | } 353 | })(); 354 | } 355 | createSWF(att, par, replaceElemIdStr); 356 | } 357 | } 358 | 359 | /* Functions to abstract and display alternative content 360 | */ 361 | function displayAltContent(obj) { 362 | if (ua.ie && ua.win && obj.readyState != 4) { 363 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, 364 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work 365 | var el = createElement("div"); 366 | obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content 367 | el.parentNode.replaceChild(abstractAltContent(obj), el); 368 | obj.style.display = "none"; 369 | (function(){ 370 | if (obj.readyState == 4) { 371 | obj.parentNode.removeChild(obj); 372 | } 373 | else { 374 | setTimeout(arguments.callee, 10); 375 | } 376 | })(); 377 | } 378 | else { 379 | obj.parentNode.replaceChild(abstractAltContent(obj), obj); 380 | } 381 | } 382 | 383 | function abstractAltContent(obj) { 384 | var ac = createElement("div"); 385 | if (ua.win && ua.ie) { 386 | ac.innerHTML = obj.innerHTML; 387 | } 388 | else { 389 | var nestedObj = obj.getElementsByTagName(OBJECT)[0]; 390 | if (nestedObj) { 391 | var c = nestedObj.childNodes; 392 | if (c) { 393 | var cl = c.length; 394 | for (var i = 0; i < cl; i++) { 395 | if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) { 396 | ac.appendChild(c[i].cloneNode(true)); 397 | } 398 | } 399 | } 400 | } 401 | } 402 | return ac; 403 | } 404 | 405 | /* Cross-browser dynamic SWF creation 406 | */ 407 | function createSWF(attObj, parObj, id) { 408 | var r, el = getElementById(id); 409 | if (ua.wk && ua.wk < 312) { return r; } 410 | if (el) { 411 | if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content 412 | attObj.id = id; 413 | } 414 | if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML 415 | var att = ""; 416 | for (var i in attObj) { 417 | if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries 418 | if (i.toLowerCase() == "data") { 419 | parObj.movie = attObj[i]; 420 | } 421 | else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword 422 | att += ' class="' + attObj[i] + '"'; 423 | } 424 | else if (i.toLowerCase() != "classid") { 425 | att += ' ' + i + '="' + attObj[i] + '"'; 426 | } 427 | } 428 | } 429 | var par = ""; 430 | for (var j in parObj) { 431 | if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries 432 | par += ''; 433 | } 434 | } 435 | el.outerHTML = '' + par + ''; 436 | objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only) 437 | r = getElementById(attObj.id); 438 | } 439 | else { // well-behaving browsers 440 | var o = createElement(OBJECT); 441 | o.setAttribute("type", FLASH_MIME_TYPE); 442 | for (var m in attObj) { 443 | if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries 444 | if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword 445 | o.setAttribute("class", attObj[m]); 446 | } 447 | else if (m.toLowerCase() != "classid") { // filter out IE specific attribute 448 | o.setAttribute(m, attObj[m]); 449 | } 450 | } 451 | } 452 | for (var n in parObj) { 453 | if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element 454 | createObjParam(o, n, parObj[n]); 455 | } 456 | } 457 | el.parentNode.replaceChild(o, el); 458 | r = o; 459 | } 460 | } 461 | return r; 462 | } 463 | 464 | function createObjParam(el, pName, pValue) { 465 | var p = createElement("param"); 466 | p.setAttribute("name", pName); 467 | p.setAttribute("value", pValue); 468 | el.appendChild(p); 469 | } 470 | 471 | /* Cross-browser SWF removal 472 | - Especially needed to safely and completely remove a SWF in Internet Explorer 473 | */ 474 | function removeSWF(id) { 475 | var obj = getElementById(id); 476 | if (obj && obj.nodeName == "OBJECT") { 477 | if (ua.ie && ua.win) { 478 | obj.style.display = "none"; 479 | (function(){ 480 | if (obj.readyState == 4) { 481 | removeObjectInIE(id); 482 | } 483 | else { 484 | setTimeout(arguments.callee, 10); 485 | } 486 | })(); 487 | } 488 | else { 489 | obj.parentNode.removeChild(obj); 490 | } 491 | } 492 | } 493 | 494 | function removeObjectInIE(id) { 495 | var obj = getElementById(id); 496 | if (obj) { 497 | for (var i in obj) { 498 | if (typeof obj[i] == "function") { 499 | obj[i] = null; 500 | } 501 | } 502 | obj.parentNode.removeChild(obj); 503 | } 504 | } 505 | 506 | /* Functions to optimize JavaScript compression 507 | */ 508 | function getElementById(id) { 509 | var el = null; 510 | try { 511 | el = doc.getElementById(id); 512 | } 513 | catch (e) {} 514 | return el; 515 | } 516 | 517 | function createElement(el) { 518 | return doc.createElement(el); 519 | } 520 | 521 | /* Updated attachEvent function for Internet Explorer 522 | - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks 523 | */ 524 | function addListener(target, eventType, fn) { 525 | target.attachEvent(eventType, fn); 526 | listenersArr[listenersArr.length] = [target, eventType, fn]; 527 | } 528 | 529 | /* Flash Player and SWF content version matching 530 | */ 531 | function hasPlayerVersion(rv) { 532 | var pv = ua.pv, v = rv.split("."); 533 | v[0] = parseInt(v[0], 10); 534 | v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0" 535 | v[2] = parseInt(v[2], 10) || 0; 536 | return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; 537 | } 538 | 539 | /* Cross-browser dynamic CSS creation 540 | - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php 541 | */ 542 | function createCSS(sel, decl, media, newStyle) { 543 | if (ua.ie && ua.mac) { return; } 544 | var h = doc.getElementsByTagName("head")[0]; 545 | if (!h) { return; } // to also support badly authored HTML pages that lack a head element 546 | var m = (media && typeof media == "string") ? media : "screen"; 547 | if (newStyle) { 548 | dynamicStylesheet = null; 549 | dynamicStylesheetMedia = null; 550 | } 551 | if (!dynamicStylesheet || dynamicStylesheetMedia != m) { 552 | // create dynamic stylesheet + get a global reference to it 553 | var s = createElement("style"); 554 | s.setAttribute("type", "text/css"); 555 | s.setAttribute("media", m); 556 | dynamicStylesheet = h.appendChild(s); 557 | if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) { 558 | dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; 559 | } 560 | dynamicStylesheetMedia = m; 561 | } 562 | // add style rule 563 | if (ua.ie && ua.win) { 564 | if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { 565 | dynamicStylesheet.addRule(sel, decl); 566 | } 567 | } 568 | else { 569 | if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { 570 | dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}")); 571 | } 572 | } 573 | } 574 | 575 | function setVisibility(id, isVisible) { 576 | if (!autoHideShow) { return; } 577 | var v = isVisible ? "visible" : "hidden"; 578 | if (isDomLoaded && getElementById(id)) { 579 | getElementById(id).style.visibility = v; 580 | } 581 | else { 582 | createCSS("#" + id, "visibility:" + v); 583 | } 584 | } 585 | 586 | /* Filter to avoid XSS attacks 587 | */ 588 | function urlEncodeIfNecessary(s) { 589 | var regex = /[\\\"<>\.;]/; 590 | var hasBadChars = regex.exec(s) != null; 591 | return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s; 592 | } 593 | 594 | /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only) 595 | */ 596 | var cleanup = function() { 597 | if (ua.ie && ua.win) { 598 | window.attachEvent("onunload", function() { 599 | // remove listeners to avoid memory leaks 600 | var ll = listenersArr.length; 601 | for (var i = 0; i < ll; i++) { 602 | listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]); 603 | } 604 | // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect 605 | var il = objIdArr.length; 606 | for (var j = 0; j < il; j++) { 607 | removeSWF(objIdArr[j]); 608 | } 609 | // cleanup library's main closures to avoid memory leaks 610 | for (var k in ua) { 611 | ua[k] = null; 612 | } 613 | ua = null; 614 | for (var l in swfobject) { 615 | swfobject[l] = null; 616 | } 617 | swfobject = null; 618 | }); 619 | } 620 | }(); 621 | 622 | return { 623 | /* Public API 624 | - Reference: http://code.google.com/p/swfobject/wiki/documentation 625 | */ 626 | registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) { 627 | if (ua.w3 && objectIdStr && swfVersionStr) { 628 | var regObj = {}; 629 | regObj.id = objectIdStr; 630 | regObj.swfVersion = swfVersionStr; 631 | regObj.expressInstall = xiSwfUrlStr; 632 | regObj.callbackFn = callbackFn; 633 | regObjArr[regObjArr.length] = regObj; 634 | setVisibility(objectIdStr, false); 635 | } 636 | else if (callbackFn) { 637 | callbackFn({success:false, id:objectIdStr}); 638 | } 639 | }, 640 | 641 | getObjectById: function(objectIdStr) { 642 | if (ua.w3) { 643 | return getObjectById(objectIdStr); 644 | } 645 | }, 646 | 647 | embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) { 648 | var callbackObj = {success:false, id:replaceElemIdStr}; 649 | if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) { 650 | setVisibility(replaceElemIdStr, false); 651 | addDomLoadEvent(function() { 652 | widthStr += ""; // auto-convert to string 653 | heightStr += ""; 654 | var att = {}; 655 | if (attObj && typeof attObj === OBJECT) { 656 | for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs 657 | att[i] = attObj[i]; 658 | } 659 | } 660 | att.data = swfUrlStr; 661 | att.width = widthStr; 662 | att.height = heightStr; 663 | var par = {}; 664 | if (parObj && typeof parObj === OBJECT) { 665 | for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs 666 | par[j] = parObj[j]; 667 | } 668 | } 669 | if (flashvarsObj && typeof flashvarsObj === OBJECT) { 670 | for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs 671 | if (typeof par.flashvars != UNDEF) { 672 | par.flashvars += "&" + k + "=" + flashvarsObj[k]; 673 | } 674 | else { 675 | par.flashvars = k + "=" + flashvarsObj[k]; 676 | } 677 | } 678 | } 679 | if (hasPlayerVersion(swfVersionStr)) { // create SWF 680 | var obj = createSWF(att, par, replaceElemIdStr); 681 | if (att.id == replaceElemIdStr) { 682 | setVisibility(replaceElemIdStr, true); 683 | } 684 | callbackObj.success = true; 685 | callbackObj.ref = obj; 686 | } 687 | else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install 688 | att.data = xiSwfUrlStr; 689 | showExpressInstall(att, par, replaceElemIdStr, callbackFn); 690 | return; 691 | } 692 | else { // show alternative content 693 | setVisibility(replaceElemIdStr, true); 694 | } 695 | if (callbackFn) { callbackFn(callbackObj); } 696 | }); 697 | } 698 | else if (callbackFn) { callbackFn(callbackObj); } 699 | }, 700 | 701 | switchOffAutoHideShow: function() { 702 | autoHideShow = false; 703 | }, 704 | 705 | ua: ua, 706 | 707 | getFlashPlayerVersion: function() { 708 | return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] }; 709 | }, 710 | 711 | hasFlashPlayerVersion: hasPlayerVersion, 712 | 713 | createSWF: function(attObj, parObj, replaceElemIdStr) { 714 | if (ua.w3) { 715 | return createSWF(attObj, parObj, replaceElemIdStr); 716 | } 717 | else { 718 | return undefined; 719 | } 720 | }, 721 | 722 | showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) { 723 | if (ua.w3 && canExpressInstall()) { 724 | showExpressInstall(att, par, replaceElemIdStr, callbackFn); 725 | } 726 | }, 727 | 728 | removeSWF: function(objElemIdStr) { 729 | if (ua.w3) { 730 | removeSWF(objElemIdStr); 731 | } 732 | }, 733 | 734 | createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) { 735 | if (ua.w3) { 736 | createCSS(selStr, declStr, mediaStr, newStyleBoolean); 737 | } 738 | }, 739 | 740 | addDomLoadEvent: addDomLoadEvent, 741 | 742 | addLoadEvent: addLoadEvent, 743 | 744 | getQueryParamValue: function(param) { 745 | var q = doc.location.search || doc.location.hash; 746 | if (q) { 747 | if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark 748 | if (param == null) { 749 | return urlEncodeIfNecessary(q); 750 | } 751 | var pairs = q.split("&"); 752 | for (var i = 0; i < pairs.length; i++) { 753 | if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { 754 | return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1))); 755 | } 756 | } 757 | } 758 | return ""; 759 | }, 760 | 761 | // For internal usage only 762 | expressInstallCallback: function() { 763 | if (isExpressInstallActive) { 764 | var obj = getElementById(EXPRESS_INSTALL_ID); 765 | if (obj && storedAltContent) { 766 | obj.parentNode.replaceChild(storedAltContent, obj); 767 | if (storedAltContentId) { 768 | setVisibility(storedAltContentId, true); 769 | if (ua.ie && ua.win) { storedAltContent.style.display = "block"; } 770 | } 771 | if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); } 772 | } 773 | isExpressInstallActive = false; 774 | } 775 | } 776 | }; 777 | }(); 778 | -------------------------------------------------------------------------------- /src/main/webapp/textLayout_2.0.0.232.swz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Red5/red5-websocket-chat/ec7bf87e222bcc91b2f0f9f699a8bf08474cf6d9/src/main/webapp/textLayout_2.0.0.232.swz --------------------------------------------------------------------------------