├── AUTHORS.md ├── LICENSE ├── README.md ├── dist ├── connect_bridge.js └── connect_bridge.min.js ├── sample ├── css │ ├── app.css │ └── font-awesome.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── index.html └── scripts │ ├── app.js │ └── connect_bridge.min.js ├── src └── connect_bridge.js └── tools ├── license_header.txt └── minify.sh /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Henry Levak -- [GitHub](https://github.com/henrylevak) | [Twitter](http://www.twitter.com/HenryLevak) 2 | 3 | Product Manager, Provider of Bubble Teas 4 | 5 | --- 6 | 7 | Jason Lai -- [GitHub](https://github.com/jlai) 8 | 9 | webOS TV second screen APIs, Cordova plugin, Cordova sample apps, Android SDK, JS Bridge, documentation 10 | 11 | --- 12 | 13 | Robert Yawn -- [GitHub](https://github.com/reyawn) | [Twitter](http://www.twitter.com/doctor_php) 14 | 15 | webOS TV second screen APIs, webOS TV media player, JS Bridge 16 | 17 | --- 18 | 19 | Jeremy White -- [GitHub](https://github.com/iheart2code) | [Twitter](https://www.twitter.com/iheart2code) 20 | 21 | iOS SDK, JS Bridge, iOS sample apps, Android sampler app, documentation 22 | 23 | --- 24 | 25 | Jeffrey Glenn -- [GitHub](https://github.com/jeffidean) 26 | 27 | Android SDK, Android sample apps, documentation 28 | 29 | --- 30 | 31 | Hyun Kook (Chris) Khang -- [GitHub](https://github.com/khk624) 32 | 33 | Android SDK, Android sample apps 34 | 35 | --- 36 | 37 | Andrew Longstaff -- [GitHub](https://github.com/longstaff) 38 | 39 | iOS SDK, documentation 40 | -------------------------------------------------------------------------------- /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 (c) 2013-2014 LG Electronics. 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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Connect SDK JavaScript Bridge 2 | Connect SDK is an open source framework that connects your mobile apps with multiple TV platforms. Because most TV platforms support a variety of protocols, Connect SDK integrates and abstracts the discovery and connectivity between all supported protocols. 3 | 4 | For more information, visit our [website](http://www.connectsdk.com/). 5 | 6 | * [General information about Connect SDK](http://www.connectsdk.com/discover/) 7 | * [Platform documentation & FAQs](http://www.connectsdk.com/docs/tv-web-app/) 8 | 9 | ##Including Connect SDK in your web app 10 | * [Create a TV Web App](http://www.connectsdk.com/docs/tv-web-app/create-connected-web-app/) 11 | * [Port a Receiver App to webOS](http://www.connectsdk.com/docs/tv-web-app/connect-my-web-app/) 12 | 13 | ##Contact 14 | * Twitter: [@ConnectSDK](https://www.twitter.com/ConnectSDK) 15 | * Ask a question with the "tv" tag on [Stack Overflow](http://stackoverflow.com/tags/tv) 16 | * General Inquiries: info@connectsdk.com 17 | * Developer Support: support@connectsdk.com 18 | * Partnerships: partners@connectsdk.com 19 | 20 | ##License 21 | Copyright (c) 2013-2014 LG Electronics. 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | > http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | -------------------------------------------------------------------------------- /dist/connect_bridge.js: -------------------------------------------------------------------------------- 1 | // 2 | // Connect SDK JavaScript Bridge 3 | // Version 1.3.1 Date: 11 Jun 2014 2:22 PM 4 | // 5 | // Created by Jeremy White on 4/16/14. 6 | // Copyright (c) 2014 LG Electronics. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | var connectsdk = (function () { 21 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | // Mixins 23 | 24 | // Event emitter 25 | var SimpleEventEmitter = { 26 | addListener: function (event, callback, context) { 27 | if (!event) { throw new Error("missing parameter: event"); } 28 | if (!callback) { throw new Error("missing parameter: callback"); } 29 | 30 | this._listeners = this._listeners || {}; 31 | if (!this._listeners) this._listeners = {}; 32 | if (!this._listeners[event]) this._listeners[event] = []; 33 | this._listeners[event].push({callback: callback, context: context}); 34 | 35 | this.emit("_addListener", event); 36 | 37 | return this; 38 | }, 39 | 40 | removeListener: function (event, callback, context) { 41 | if (this._listeners && this._listeners[event]) { 42 | this._listeners[event] = this._listeners[event].filter(function (l) { 43 | return (callback && callback !== l.callback) && (context && context !== l.context); 44 | }); 45 | } 46 | 47 | this.emit("_removeListener", event); 48 | 49 | return this; 50 | }, 51 | 52 | hasListeners: function (event) { 53 | if (event) { 54 | return (this._listeners && this._listeners[event] && this._listeners[event].length > 0); 55 | } else { 56 | for (event in this._listeners) { 57 | if (event[0] !== "_" && this._listeners.hasOwnProperty(event) && this._listeners[event].length > 0) { 58 | return true; 59 | } 60 | } 61 | return false; 62 | } 63 | }, 64 | 65 | emit: function (event) { 66 | var listeners = this._listeners && this._listeners[event]; 67 | var args = Array.prototype.slice.call(arguments, 1); 68 | 69 | // upper-case first char 70 | event = event.charAt(0).toUpperCase() + event.slice(1); 71 | 72 | if (this["on" + event]) { 73 | this["on" + event].apply(this, args); 74 | } 75 | 76 | if (listeners) { 77 | listeners.forEach(function (l) { 78 | l.callback.apply(l.context || null, args); 79 | }); 80 | } 81 | }, 82 | 83 | on: function (event, callback, context) { 84 | return this.addListener(event, callback, context); 85 | }, 86 | 87 | off: function (event, callback, context) { 88 | return this.removeListener(event, callback, context); 89 | } 90 | }; 91 | 92 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 93 | // ConnectManager 94 | 95 | var ConnectManager = createClass({ 96 | mixins: [SimpleEventEmitter], 97 | 98 | statics: { 99 | PlatformType: { 100 | DEFAULT: "Default", 101 | AIRPLAY: "AirPlay", 102 | GOOGLE_CAST: "GoogleCast", 103 | WEBOS_NATIVE: "WebOSNative", 104 | WEBOS_WEB_APP: "WebOSWebApp" 105 | }, 106 | 107 | EventType: { 108 | MESSAGE: "message", 109 | PAUSE: "pause", 110 | PLAY: "play", 111 | READY: "ready", 112 | STOP: "stop", 113 | STATUS: "mediaStatusUpdate", 114 | JOIN: "join", 115 | DEPART: "depart" 116 | } 117 | }, 118 | 119 | mediaEvents: { 120 | loadstart: "buffering", 121 | playing: "playing", 122 | waiting: "buffering", 123 | abort: "finished", 124 | ended: "finished", 125 | play: "playing", 126 | pause: "paused" 127 | }, 128 | 129 | constructor: function () { 130 | this.handleMediaEvent = this.handleMediaEvent.bind(this); 131 | 132 | extend(this, platforms[this._detectPlatform()]); 133 | }, 134 | 135 | setImageElement: function (element) { 136 | // Register new image element 137 | if (element) { 138 | this.imageElement = element; 139 | } 140 | }, 141 | 142 | setMediaElement: function (element) { 143 | // Unregister existing media element 144 | this.mediaElement && this.unregisterMediaEvents(this.mediaElement); 145 | 146 | // Register new media element 147 | if (element) { 148 | this.registerMediaEvents(element); 149 | this.mediaElement = element; 150 | this.mediaElement.autoPlay = true; 151 | this.setMediaStatus("idle"); 152 | } 153 | }, 154 | 155 | setMediaStatus: function (status) { 156 | this.mediaStatus = status; 157 | this.emit(ConnectManager.EventType.STATUS, status); 158 | }, 159 | 160 | registerMediaEvents: function (element) { 161 | if (element) { 162 | for (var key in this.mediaEvents) { 163 | this.mediaEvents.hasOwnProperty(key) && element.addEventListener(key, this.handleMediaEvent, false); 164 | } 165 | } 166 | }, 167 | 168 | unregisterMediaEvents: function (element) { 169 | if (element) { 170 | for (var key in this.mediaEvents) { 171 | this.mediaEvents.hasOwnProperty(key) && element.removeEventListener(key, this.handleMediaEvent, false); 172 | } 173 | } 174 | }, 175 | 176 | handleMediaEvent: function (evt) { 177 | this.mediaEvents.hasOwnProperty(evt.type) && this.setMediaStatus(this.mediaEvents[evt.type]); 178 | }, 179 | 180 | handleReady: function (evt) { 181 | this.emit(ConnectManager.EventType.READY); 182 | }, 183 | 184 | handleJoin: function (client) { 185 | this.emit(ConnectManager.EventType.JOIN, client); 186 | }, 187 | 188 | handleDepart: function (client) { 189 | this.emit(ConnectManager.EventType.DEPART, client); 190 | }, 191 | 192 | _detectPlatform: function() { 193 | var userAgent = navigator.userAgent.toLowerCase(); 194 | this.platformType = ConnectManager.PlatformType.DEFAULT; 195 | 196 | if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) 197 | this.platformType = ConnectManager.PlatformType.AIRPLAY; 198 | else if (userAgent.indexOf('crkey') > 0 && cast != null) 199 | this.platformType = ConnectManager.PlatformType.GOOGLE_CAST; 200 | else if (userAgent.indexOf('tv') >= 0 && (userAgent.indexOf('webos') >= 0) || (userAgent.indexOf('web0s') >= 0)) 201 | { 202 | if (window.PalmServiceBridge) 203 | this.platformType = ConnectManager.PlatformType.WEBOS_NATIVE; 204 | else 205 | this.platformType = ConnectManager.PlatformType.WEBOS_WEB_APP; 206 | } 207 | return this.platformType; 208 | }, 209 | 210 | init: nop, 211 | 212 | sendMessage: nop, 213 | 214 | broadcastMessage: nop 215 | }); 216 | 217 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 218 | // Platforms 219 | 220 | var platforms = {}; 221 | 222 | // Default 223 | platforms.Default = { 224 | interactive: false, 225 | init: nop, 226 | sendMessage: nop, 227 | broadcastMessage: nop 228 | }; 229 | 230 | // Base media player (JSON media playback & control commands) 231 | 232 | var BaseMediaPlayer = { 233 | 234 | onLoadImage: function (image) { 235 | var imageElement = this.imageElement; 236 | if (imageElement && image && image.mediaURL) { 237 | console.log("Loading image", image.mediaURL); 238 | imageElement.src = image.mediaURL; 239 | } else { 240 | console.log("Failed to load image"); 241 | } 242 | }, 243 | 244 | onLoadMedia: function (media) { 245 | var mediaElement = this.mediaElement; 246 | if (mediaElement && media && media.mediaURL) { 247 | console.log("Loading", media.mediaURL); 248 | // TODO: pull metadata 249 | mediaElement.src = media.mediaURL; 250 | mediaElement.load(); 251 | } else { 252 | console.log("Failed to load media"); 253 | } 254 | }, 255 | 256 | handleDisplayImage: function (msgData) { 257 | var from = msgData.from; 258 | var mediaCommand = msgData.message.mediaCommand; 259 | var commandType = mediaCommand.type; 260 | var requestId = mediaCommand.requestId; 261 | 262 | this.emit('loadImage', mediaCommand); 263 | 264 | this.sendMessage(from, { 265 | contentType: 'connectsdk.mediaCommandResponse', 266 | mediaCommandResponse: { 267 | type: commandType, 268 | requestId: requestId 269 | } 270 | }); 271 | }, 272 | 273 | handleGetDuration: function (msgData) { 274 | var from = msgData.from; 275 | var commandType = msgData.message.mediaCommand.type; 276 | var requestId = msgData.message.mediaCommand.requestId; 277 | var mediaElement = this.mediaElement; 278 | var duration = (mediaElement && mediaElement.duration) || 0; 279 | 280 | this.sendMessage(from, { 281 | contentType: 'connectsdk.mediaCommandResponse', 282 | mediaCommandResponse: { 283 | type: commandType, 284 | duration: duration, 285 | requestId: requestId 286 | } 287 | }); 288 | }, 289 | 290 | handleGetPosition: function (msgData) { 291 | var from = msgData.from; 292 | var commandType = msgData.message.mediaCommand.type; 293 | var requestId = msgData.message.mediaCommand.requestId; 294 | var mediaElement = this.mediaElement; 295 | var currentTime = (mediaElement && mediaElement.currentTime) || 0; 296 | 297 | this.sendMessage(from, { 298 | contentType: 'connectsdk.mediaCommandResponse', 299 | mediaCommandResponse: { 300 | type: commandType, 301 | position: currentTime, 302 | requestId: requestId 303 | } 304 | }); 305 | }, 306 | 307 | handleMediaStatusUpdate: function (requestId) { 308 | var playState = this.mediaStatus; 309 | var currentTime = 0; 310 | var duration = 0; 311 | var mediaElement = this.mediaElement; 312 | 313 | if (mediaElement) { 314 | currentTime = mediaElement.currentTime; 315 | 316 | if (mediaElement.duration != NaN) 317 | duration = mediaElement.duration; 318 | 319 | if (playState == null) 320 | return; 321 | 322 | this.broadcastMessage({ 323 | contentType: 'connectsdk.mediaEvent', 324 | mediaEvent: { 325 | type: 'playState', 326 | playState: playState, 327 | position: currentTime, 328 | duration: duration, 329 | requestId: requestId ? requestId : -1 330 | } 331 | }); 332 | } 333 | }, 334 | 335 | handlePlayMedia: function (msgData) { 336 | var from = msgData.from; 337 | var mediaCommand = msgData.message.mediaCommand; 338 | var commandType = mediaCommand.type; 339 | var requestId = mediaCommand.requestId; 340 | 341 | this.emit('loadMedia', mediaCommand); 342 | 343 | this.sendMessage(from, { 344 | contentType: 'connectsdk.mediaCommandResponse', 345 | mediaCommandResponse: { 346 | type: commandType, 347 | requestId: requestId 348 | } 349 | }); 350 | }, 351 | 352 | handleSeek: function (msgData) { 353 | var from = msgData.from; 354 | var mediaCommand = msgData.message.mediaCommand; 355 | var commandType = mediaCommand.type; 356 | var requestId = mediaCommand.requestId; 357 | var position = mediaCommand.position; 358 | 359 | if (position) { 360 | var mediaElement = this.mediaElement; 361 | mediaElement && (mediaElement.currentTime = position); 362 | this.handleMediaStatusUpdate(-1); 363 | } 364 | 365 | this.sendMessage(from, { 366 | contentType: 'connectsdk.mediaCommandResponse', 367 | mediaCommandResponse: { 368 | type: commandType, 369 | requestId: requestId 370 | } 371 | }); 372 | }, 373 | 374 | handlePlay: function (msgData) { 375 | var from = msgData.from; 376 | var mediaCommand = msgData.message.mediaCommand; 377 | var commandType = mediaCommand.type; 378 | var requestId = mediaCommand.requestId; 379 | 380 | var mediaElement = this.mediaElement; 381 | mediaElement && mediaElement.play(); 382 | 383 | this.sendMessage(from, { 384 | contentType: 'connectsdk.mediaCommandResponse', 385 | mediaCommandResponse: { 386 | type: commandType, 387 | requestId: requestId 388 | } 389 | }); 390 | }, 391 | 392 | handlePause: function (msgData) { 393 | var from = msgData.from; 394 | var mediaCommand = msgData.message.mediaCommand; 395 | var commandType = mediaCommand.type; 396 | var requestId = mediaCommand.requestId; 397 | 398 | var mediaElement = this.mediaElement; 399 | mediaElement && mediaElement.pause(); 400 | 401 | this.sendMessage(from, { 402 | contentType: 'connectsdk.mediaCommandResponse', 403 | mediaCommandResponse: { 404 | type: commandType, 405 | requestId: requestId 406 | } 407 | }); 408 | }, 409 | 410 | handleMessage: function (msgData) { 411 | var contentType = null; 412 | 413 | if (msgData != null && msgData.message != null) { 414 | contentType = msgData.message.contentType; 415 | 416 | if (contentType == null) { 417 | try { 418 | contentType = JSON.parse(msgData.message).contentType; 419 | } catch (ex) { 420 | // don't need to do anything here 421 | } 422 | } 423 | } 424 | 425 | switch (contentType) { 426 | case "connectsdk.mediaCommand": 427 | this.handleMediaCommand(msgData); 428 | break; 429 | 430 | case "connectsdk.serviceCommand": 431 | this.handleServiceCommand(msgData); 432 | break; 433 | 434 | default: 435 | this.emit(ConnectManager.EventType.MESSAGE, msgData); 436 | } 437 | }, 438 | 439 | handleMediaCommand: function (msgData) { 440 | var mediaCommand = msgData.message.mediaCommand; 441 | if (!mediaCommand) { 442 | return; 443 | } 444 | 445 | var commandType = mediaCommand.type; 446 | console.log('processing mediaCommand ' + JSON.stringify(mediaCommand) + ' of type ' + commandType); 447 | 448 | switch (commandType) { 449 | case "displayImage": 450 | this.handleDisplayImage(msgData); 451 | break; 452 | case "getDuration": 453 | this.handleGetDuration(msgData); 454 | break; 455 | case "getPosition": 456 | this.handleGetPosition(msgData); 457 | break; 458 | case "playMedia": 459 | this.handlePlayMedia(msgData); 460 | break; 461 | case "seek": 462 | this.handleSeek(msgData); 463 | break; 464 | case "play": 465 | this.handlePlay(msgData); 466 | break; 467 | case "pause": 468 | this.handlePause(msgData); 469 | break; 470 | } 471 | }, 472 | 473 | handleServiceCommand: function (msgData) { 474 | var serviceCommand = msgData.message.serviceCommand; 475 | if (!serviceCommand) { 476 | return; 477 | } 478 | 479 | var commandType = serviceCommand.type; 480 | console.log('processing serviceCommand ' + JSON.stringify(serviceCommand) + ' of type ' + commandType); 481 | 482 | switch (commandType) { 483 | case "close": 484 | // this is a hack to circumvent the fact that window.close() doesn't work with the webOS app type 485 | var newWindow = window.open(window.location, '_self'); 486 | 487 | if (newWindow != null) 488 | newWindow.close(); 489 | else 490 | window.close(); 491 | break; 492 | } 493 | } 494 | }; 495 | 496 | // webOS 497 | var WebOSCommon = extend({ 498 | interactive: true, 499 | init: function () { 500 | window.addEventListener("keydown", this.handleKeyDown.bind(this)); 501 | this.on(ConnectManager.EventType.STATUS, this.handleMediaStatusUpdate.bind(this)); 502 | 503 | this.webOSAppChannels = new WebOSAppChannels(); 504 | this.webOSAppChannels.on('message', this.handleMessage.bind(this)); 505 | this.webOSAppChannels.on('ready', this.handleReady.bind(this)); 506 | this.webOSAppChannels.on('join', this.handleJoin.bind(this)); 507 | this.webOSAppChannels.on('depart', this.handleDepart.bind(this)); 508 | this.webOSAppChannels.start(); 509 | }, 510 | 511 | sendMessage: function (to, message) { 512 | this.webOSAppChannels.sendMessage(to, message); 513 | }, 514 | 515 | broadcastMessage: function (message) { 516 | this.webOSAppChannels.broadcastMessage(message); 517 | }, 518 | 519 | handleKeyDown: function (evt) { 520 | if (!this.mediaElement) { 521 | return; 522 | } 523 | 524 | switch (evt.keyCode) { 525 | case 415: // PLAY 526 | console.log(this.name + " :: play command received"); 527 | this.mediaElement.play(); 528 | this.emit(ConnectManager.EventType.PLAY); 529 | break; 530 | 531 | case 19: // PAUSE 532 | console.log(this.name + " :: pause command received"); 533 | this.mediaElement.pause(); 534 | this.emit(ConnectManager.EventType.PAUSE); 535 | break; 536 | 537 | case 413: // STOP 538 | console.log(this.name + " :: stop command received"); 539 | this.emit(ConnectManager.EventType.STOP); 540 | break; 541 | } 542 | } 543 | }, BaseMediaPlayer); 544 | 545 | platforms.WebOSNative = extend({ 546 | name: "webOS Native Web App" 547 | }, WebOSCommon); 548 | 549 | platforms.WebOSWebApp = extend({ 550 | name: "webOS Web App" 551 | }, WebOSCommon); 552 | 553 | // AirPlay 554 | platforms.AirPlay = extend({ 555 | name: "AirPlay", 556 | interactive: true, 557 | 558 | init: function() { 559 | this.on(ConnectManager.EventType.STATUS, this.handleMediaStatusUpdate.bind(this)); 560 | }, 561 | 562 | sendMessage: function (to, message) { 563 | // AirPlay does not have p2p support, so we'll just 'broadcast' this message 564 | this.broadcastMessage(message); 565 | }, 566 | 567 | broadcastMessage: function (message) { 568 | var messageString; 569 | 570 | if (typeof message == 'string') 571 | messageString = message; 572 | else 573 | messageString = JSON.stringify(message); 574 | 575 | var iframe = document.createElement('IFRAME'); 576 | iframe.setAttribute('src', 'connectsdk://' + messageString); 577 | document.documentElement.appendChild(iframe); 578 | iframe.parentNode.removeChild(iframe); 579 | iframe = null; 580 | } 581 | }, BaseMediaPlayer), 582 | 583 | // Google Cast 584 | platforms.GoogleCast = { 585 | name: "Google Cast", 586 | interactive: false, 587 | init: function () { 588 | var origSetMediaElement = this.setMediaElement; 589 | this.setMediaElement = function (element) { 590 | origSetMediaElement.apply(this, arguments); 591 | this._setCastElement(element); 592 | }; 593 | 594 | this._setCastElement(this.mediaElement); 595 | window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); 596 | window.castReceiverManager.addEventListener("ready", this._handleReady.bind(this)); 597 | 598 | window.castMessageBus = window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk"); 599 | window.castMessageBus.addEventListener("message", this.handleMessage.bind(this)); 600 | 601 | window.castReceiverManager.start(); 602 | }, 603 | 604 | _handleReady: function(evt) { 605 | window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_CONNECTED, this.handleSenderConnected.bind(this)); 606 | window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_DISCONNECTED, this.handleSenderDisconnected.bind(this)); 607 | 608 | this.handleReady(evt); 609 | }, 610 | 611 | _setCastElement: function (element) { 612 | if (!element) { 613 | return; 614 | } 615 | if (!window.castMediaManager) { 616 | window.castMediaManager = new cast.receiver.MediaManager(element); 617 | } else { 618 | window.castMediaManager.setMediaElement(element); 619 | } 620 | }, 621 | 622 | handleMessage: function (evt) { 623 | var message; 624 | try { 625 | message = JSON.parse(evt.data); 626 | } catch (ex) { 627 | message = evt.data; 628 | } 629 | 630 | this.emit(ConnectManager.EventType.MESSAGE, { from: evt.senderId, message: message }); 631 | }, 632 | 633 | sendMessage: function (to, message) { 634 | var messageString; 635 | 636 | if (typeof message == 'string') 637 | window.castMessageBus.send(to, message); 638 | else 639 | { 640 | var messageString = JSON.stringify(message); 641 | 642 | if (messageString) 643 | window.castMessageBus.send(to, messageString); 644 | } 645 | }, 646 | 647 | broadcastMessage: function (message) { 648 | var messageString; 649 | 650 | if (typeof message == 'string') 651 | window.castMessageBus.broadcast(message); 652 | else 653 | { 654 | var messageString = JSON.stringify(message); 655 | 656 | if (messageString) 657 | window.castMessageBus.broadcast(messageString); 658 | } 659 | }, 660 | 661 | handleSenderConnected: function(sender) { 662 | if (sender == null || sender.senderId == null) 663 | return; 664 | 665 | sender.id = sender.senderId; 666 | 667 | this.emit(ConnectManager.EventType.JOIN, sender); 668 | }, 669 | 670 | handleSenderDisconnected: function(sender) { 671 | if (sender == null || sender.senderId == null) 672 | return; 673 | 674 | sender.id = sender.senderId; 675 | 676 | this.emit(ConnectManager.EventType.DEPART, sender); 677 | } 678 | }; 679 | 680 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 681 | // WebOSAppChannels 682 | 683 | var WebOSAppChannelsInstance; 684 | var WebOSAppChannels = createClass({ 685 | mixins: [SimpleEventEmitter], 686 | 687 | constructor: function() { 688 | // Singleton logic 689 | if (WebOSAppChannelsInstance) { 690 | return WebOSAppChannelsInstance; 691 | } 692 | WebOSAppChannelsInstance = this; 693 | 694 | this.stopRequested = false; 695 | this.ws = null; 696 | this.channels = []; 697 | 698 | if (window.PalmServiceBridge) { 699 | var statusSubscription; 700 | var getChannelRequest; 701 | 702 | var GATEWAY_SERVICE = "com.webos.service.secondscreen.gateway"; 703 | var CREATE_APPCHANNEL_URI = "luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel"; 704 | var REGISTER_SERVER_STATUS_URI = "luna://com.palm.bus/signal/registerServerStatus"; 705 | 706 | function callService(uri, params, callback) { 707 | var request = new window.PalmServiceBridge(); 708 | 709 | request.onservicecallback = function (responseString) { 710 | callback(JSON.parse(responseString)); 711 | }; 712 | 713 | request.call(uri, JSON.stringify(params || {})); 714 | 715 | return request; 716 | } 717 | 718 | this.getAppChannelWebSocket = function (callback) { 719 | statusSubcription = callService(REGISTER_SERVER_STATUS_URI, { 720 | "subscribe": true, serviceName: GATEWAY_SERVICE 721 | }, function (response) { 722 | if (response && response.connected) { 723 | getChannelRequest = callService(CREATE_APPCHANNEL_URI, {}, function (response) { 724 | if (response.socketUrl) { 725 | callback(new WebSocket(response.socketUrl)); 726 | } 727 | }); 728 | } 729 | }); 730 | }; 731 | } else { 732 | // Look for "webOSAppChannelSocketUrl" parameter 733 | var foundSocketUrl = false; 734 | if (window.location.search) { 735 | console.log("found params: ", window.location.search); 736 | /** 737 | * @author Konstantin Kitmanov 738 | * May be freely distributed under the MIT license. 739 | */ 740 | function parse(str) { 741 | var chunks = str.split('&'), 742 | dict = {}, 743 | chunk; 744 | for (var i = 0, len = chunks.length; i < len; i++) { 745 | chunk = chunks[i].split('='); 746 | dict[chunk[0]] = decodeURIComponent(chunk[1]); 747 | } 748 | 749 | return dict; 750 | } 751 | 752 | var parsed = parse(window.location.search.substr(1)); 753 | 754 | if (parsed.webOSAppChannelSocketUrl) { 755 | console.log("found websocket URL: ", parsed.webOSAppChannelSocketUrl); 756 | foundSocketUrl = true; 757 | 758 | this.getAppChannelWebSocket = function (callback) { 759 | callback(new WebSocket(parsed.webOSAppChannelSocketUrl)); 760 | }; 761 | } 762 | } 763 | 764 | if (!foundSocketUrl && window.NetCastCreateAppChannel) { 765 | var callbackName = "_webOSCreateAppChannelCallback"; 766 | 767 | this.getAppChannelWebSocket = function (callback) { 768 | 769 | window[callbackName] = function (response) { 770 | delete window[callbackName]; 771 | //console.log("got NetCastCreateAppChannel response: " + JSON.stringify(response)); 772 | 773 | if (response.socketUrl) { 774 | callback(new WebSocket(response.socketUrl)); 775 | } 776 | }; 777 | 778 | window.NetCastCreateAppChannel('{}', callbackName, false); 779 | }; 780 | } 781 | } 782 | }, 783 | 784 | getAppChannelWebSocket: function (callback) { 785 | console.error("app channel socket not supported"); 786 | }, 787 | 788 | start: function () { 789 | this.stopRequested = false; 790 | var self = this; 791 | 792 | !this.ws && this.getAppChannelWebSocket(function (socket) { 793 | if (self.stopRequested) { 794 | self.stop(); 795 | return; 796 | } 797 | 798 | self.ws = socket; 799 | 800 | self.ws.onopen = function (event) { 801 | console.log("websocket opened"); 802 | self.emit('ready', event); 803 | }; 804 | 805 | self.ws.onerror = function (error) { 806 | console.log("websocket error:", error); 807 | }; 808 | 809 | self.ws.onmessage = function (event) { 810 | try { 811 | var message = JSON.parse(event.data); 812 | } catch (e) { 813 | return; // Ignore the message if it doesn't parse. 814 | } 815 | console.log("got message: " + JSON.stringify(message)); 816 | 817 | switch (message.type) { 818 | case "p2p": 819 | self._handleP2PMessage(message); 820 | break; 821 | case "p2p.depart": 822 | self._handleP2PDepart(message); 823 | break; 824 | case "p2p.join": 825 | self._handleP2PJoin(message); 826 | break; 827 | case "p2p.join-request": 828 | self._handleP2PJoinRequest(message); 829 | break; 830 | } 831 | }; 832 | 833 | self.ws.onclose = function () { 834 | self.ws = null; 835 | }; 836 | }); 837 | }, 838 | 839 | stop: function () { 840 | this.stopRequested = true; 841 | 842 | if (this.ws) { 843 | this.ws.close(); 844 | } 845 | 846 | this._destroy(); 847 | }, 848 | 849 | sendMessage: function(to, message) { 850 | var messageData = { 851 | type: "p2p", 852 | to: to, // TODO: do we need to sanitize/check this value? 853 | payload: message 854 | }; 855 | 856 | this._send(messageData); 857 | }, 858 | 859 | broadcastMessage: function(message) { 860 | var messageData = { 861 | type: "p2p", 862 | payload: message 863 | }; 864 | 865 | this._send(messageData); 866 | }, 867 | 868 | _send: function (message) { 869 | if (this.ws && message) { 870 | console.log("sending message: ", message); 871 | this.ws.send(JSON.stringify(message)); 872 | } 873 | }, 874 | 875 | _destroy: function () { 876 | if (this.ws) { 877 | this.ws = null; 878 | } 879 | }, 880 | 881 | _handleP2PMessage: function (message) { 882 | var payload = message.payload; 883 | if (!payload) { 884 | return; 885 | } 886 | 887 | console.log('processing message payload ' + JSON.stringify(payload)); 888 | this.emit('message', {from: message.from, message: message.payload}); 889 | }, 890 | 891 | _handleP2PJoinRequest: function (message) { 892 | var payload = message.payload; 893 | if (!payload) { 894 | return; 895 | } 896 | 897 | this._send({ 898 | type: "p2p.join-response", 899 | payload: { 900 | allowJoin: true, 901 | requestId: payload.requestId 902 | } 903 | }); 904 | }, 905 | 906 | _handleP2PJoin: function (message) { 907 | var client = message.client; 908 | if (!client) { 909 | return; 910 | } 911 | 912 | console.log('processing client join ' + JSON.stringify(client)); 913 | this.emit(ConnectManager.EventType.JOIN, client); 914 | }, 915 | 916 | _handleP2PDepart: function (message) { 917 | var clientId = message.from; 918 | if (!clientId) { 919 | return; 920 | } 921 | 922 | console.log('processing client departure ' + clientId); 923 | this.emit(ConnectManager.EventType.DEPART, { id: clientId }); 924 | } 925 | }); 926 | 927 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 928 | // Helpers 929 | 930 | function getParameterByName(name) { 931 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 932 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 933 | results = regex.exec(location.search); 934 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 935 | } 936 | 937 | // very simple class maker 938 | function createClass(desc) { 939 | var constructor; 940 | 941 | if (desc.constructor) { 942 | constructor = desc.constructor; 943 | delete desc.constructor; 944 | } else { 945 | constructor = function () {}; 946 | throw new Error("no constructor"); 947 | } 948 | 949 | var prototype = constructor.prototype; 950 | 951 | if (desc.mixins) { 952 | desc.mixins.forEach(function (mixin) { 953 | extend(prototype, mixin); 954 | }); 955 | delete desc.mixins; 956 | } 957 | 958 | if (desc.statics) { 959 | extend(constructor, desc.statics); 960 | delete desc.statics; 961 | } 962 | 963 | extend(prototype, desc); 964 | return constructor; 965 | } 966 | 967 | function extend(a, b) { 968 | for (var key in b) { 969 | if (b.hasOwnProperty(key) && !a.hasOwnProperty(key)) { 970 | a[key] = b[key] 971 | } 972 | } 973 | return a; 974 | } 975 | 976 | function nop() {} 977 | 978 | return { 979 | ConnectManager: ConnectManager, 980 | WebOSAppChannels: WebOSAppChannels 981 | } 982 | })(); -------------------------------------------------------------------------------- /dist/connect_bridge.min.js: -------------------------------------------------------------------------------- 1 | // 2 | // Connect SDK JavaScript Bridge 3 | // Version 1.3.1 Date: 11 Jun 2014 2:22 PM 4 | // 5 | // Created by Jeremy White on 4/16/14. 6 | // Copyright (c) 2014 LG Electronics. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | var connectsdk=function(){function e(e){var n;if(!e.constructor)throw n=function(){},Error("no constructor");n=e.constructor,delete e.constructor;var s=n.prototype;return e.mixins&&(e.mixins.forEach(function(e){t(s,e)}),delete e.mixins),e.statics&&(t(n,e.statics),delete e.statics),t(s,e),n}function t(e,t){for(var n in t)t.hasOwnProperty(n)&&!e.hasOwnProperty(n)&&(e[n]=t[n]);return e}function n(){}var s={addListener:function(e,t,n){if(!e)throw Error("missing parameter: event");if(!t)throw Error("missing parameter: callback");return this._listeners=this._listeners||{},this._listeners||(this._listeners={}),this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push({callback:t,context:n}),this.emit("_addListener",e),this},removeListener:function(e,t,n){return this._listeners&&this._listeners[e]&&(this._listeners[e]=this._listeners[e].filter(function(e){return t&&t!==e.callback&&n&&n!==e.context})),this.emit("_removeListener",e),this},hasListeners:function(e){if(e)return this._listeners&&this._listeners[e]&&this._listeners[e].length>0;for(e in this._listeners)if("_"!==e[0]&&this._listeners.hasOwnProperty(e)&&this._listeners[e].length>0)return!0;return!1},emit:function(e){var t=this._listeners&&this._listeners[e],n=Array.prototype.slice.call(arguments,1);e=e.charAt(0).toUpperCase()+e.slice(1),this["on"+e]&&this["on"+e].apply(this,n),t&&t.forEach(function(e){e.callback.apply(e.context||null,n)})},on:function(e,t,n){return this.addListener(e,t,n)},off:function(e,t,n){return this.removeListener(e,t,n)}},a=e({mixins:[s],statics:{PlatformType:{DEFAULT:"Default",AIRPLAY:"AirPlay",GOOGLE_CAST:"GoogleCast",WEBOS_NATIVE:"WebOSNative",WEBOS_WEB_APP:"WebOSWebApp"},EventType:{MESSAGE:"message",PAUSE:"pause",PLAY:"play",READY:"ready",STOP:"stop",STATUS:"mediaStatusUpdate",JOIN:"join",DEPART:"depart"}},mediaEvents:{loadstart:"buffering",playing:"playing",waiting:"buffering",abort:"finished",ended:"finished",play:"playing",pause:"paused"},constructor:function(){this.handleMediaEvent=this.handleMediaEvent.bind(this),t(this,i[this._detectPlatform()])},setImageElement:function(e){e&&(this.imageElement=e)},setMediaElement:function(e){this.mediaElement&&this.unregisterMediaEvents(this.mediaElement),e&&(this.registerMediaEvents(e),this.mediaElement=e,this.mediaElement.autoPlay=!0,this.setMediaStatus("idle"))},setMediaStatus:function(e){this.mediaStatus=e,this.emit(a.EventType.STATUS,e)},registerMediaEvents:function(e){if(e)for(var t in this.mediaEvents)this.mediaEvents.hasOwnProperty(t)&&e.addEventListener(t,this.handleMediaEvent,!1)},unregisterMediaEvents:function(e){if(e)for(var t in this.mediaEvents)this.mediaEvents.hasOwnProperty(t)&&e.removeEventListener(t,this.handleMediaEvent,!1)},handleMediaEvent:function(e){this.mediaEvents.hasOwnProperty(e.type)&&this.setMediaStatus(this.mediaEvents[e.type])},handleReady:function(){this.emit(a.EventType.READY)},handleJoin:function(e){this.emit(a.EventType.JOIN,e)},handleDepart:function(e){this.emit(a.EventType.DEPART,e)},_detectPlatform:function(){var e=navigator.userAgent.toLowerCase();return this.platformType=a.PlatformType.DEFAULT,/(iPad|iPhone|iPod)/g.test(navigator.userAgent)?this.platformType=a.PlatformType.AIRPLAY:e.indexOf("crkey")>0&&null!=cast?this.platformType=a.PlatformType.GOOGLE_CAST:(e.indexOf("tv")>=0&&e.indexOf("webos")>=0||e.indexOf("web0s")>=0)&&(this.platformType=window.PalmServiceBridge?a.PlatformType.WEBOS_NATIVE:a.PlatformType.WEBOS_WEB_APP),this.platformType},init:n,sendMessage:n,broadcastMessage:n}),i={};i.Default={interactive:!1,init:n,sendMessage:n,broadcastMessage:n};var o={onLoadImage:function(e){var t=this.imageElement;t&&e&&e.mediaURL?(console.log("Loading image",e.mediaURL),t.src=e.mediaURL):console.log("Failed to load image")},onLoadMedia:function(e){var t=this.mediaElement;t&&e&&e.mediaURL?(console.log("Loading",e.mediaURL),t.src=e.mediaURL,t.load()):console.log("Failed to load media")},handleDisplayImage:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId;this.emit("loadImage",n),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleGetDuration:function(e){var t=e.from,n=e.message.mediaCommand.type,s=e.message.mediaCommand.requestId,a=this.mediaElement,i=a&&a.duration||0;this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:n,duration:i,requestId:s}})},handleGetPosition:function(e){var t=e.from,n=e.message.mediaCommand.type,s=e.message.mediaCommand.requestId,a=this.mediaElement,i=a&&a.currentTime||0;this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:n,position:i,requestId:s}})},handleMediaStatusUpdate:function(e){var t=this.mediaStatus,n=0,s=0,a=this.mediaElement;if(a){if(n=a.currentTime,0/0!=a.duration&&(s=a.duration),null==t)return;this.broadcastMessage({contentType:"connectsdk.mediaEvent",mediaEvent:{type:"playState",playState:t,position:n,duration:s,requestId:e?e:-1}})}},handlePlayMedia:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId;this.emit("loadMedia",n),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleSeek:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=n.position;if(i){var o=this.mediaElement;o&&(o.currentTime=i),this.handleMediaStatusUpdate(-1)}this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handlePlay:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=this.mediaElement;i&&i.play(),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handlePause:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=this.mediaElement;i&&i.pause(),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleMessage:function(e){var t=null;if(null!=e&&null!=e.message&&(t=e.message.contentType,null==t))try{t=JSON.parse(e.message).contentType}catch(n){}switch(t){case"connectsdk.mediaCommand":this.handleMediaCommand(e);break;case"connectsdk.serviceCommand":this.handleServiceCommand(e);break;default:this.emit(a.EventType.MESSAGE,e)}},handleMediaCommand:function(e){var t=e.message.mediaCommand;if(t){var n=t.type;switch(console.log("processing mediaCommand "+JSON.stringify(t)+" of type "+n),n){case"displayImage":this.handleDisplayImage(e);break;case"getDuration":this.handleGetDuration(e);break;case"getPosition":this.handleGetPosition(e);break;case"playMedia":this.handlePlayMedia(e);break;case"seek":this.handleSeek(e);break;case"play":this.handlePlay(e);break;case"pause":this.handlePause(e)}}},handleServiceCommand:function(e){var t=e.message.serviceCommand;if(t){var n=t.type;switch(console.log("processing serviceCommand "+JSON.stringify(t)+" of type "+n),n){case"close":var s=window.open(window.location,"_self");null!=s?s.close():window.close()}}}},r=t({interactive:!0,init:function(){window.addEventListener("keydown",this.handleKeyDown.bind(this)),this.on(a.EventType.STATUS,this.handleMediaStatusUpdate.bind(this)),this.webOSAppChannels=new c,this.webOSAppChannels.on("message",this.handleMessage.bind(this)),this.webOSAppChannels.on("ready",this.handleReady.bind(this)),this.webOSAppChannels.on("join",this.handleJoin.bind(this)),this.webOSAppChannels.on("depart",this.handleDepart.bind(this)),this.webOSAppChannels.start()},sendMessage:function(e,t){this.webOSAppChannels.sendMessage(e,t)},broadcastMessage:function(e){this.webOSAppChannels.broadcastMessage(e)},handleKeyDown:function(e){if(this.mediaElement)switch(e.keyCode){case 415:console.log(this.name+" :: play command received"),this.mediaElement.play(),this.emit(a.EventType.PLAY);break;case 19:console.log(this.name+" :: pause command received"),this.mediaElement.pause(),this.emit(a.EventType.PAUSE);break;case 413:console.log(this.name+" :: stop command received"),this.emit(a.EventType.STOP)}}},o);i.WebOSNative=t({name:"webOS Native Web App"},r),i.WebOSWebApp=t({name:"webOS Web App"},r),i.AirPlay=t({name:"AirPlay",interactive:!0,init:function(){this.on(a.EventType.STATUS,this.handleMediaStatusUpdate.bind(this))},sendMessage:function(e,t){this.broadcastMessage(t)},broadcastMessage:function(e){var t;t="string"==typeof e?e:JSON.stringify(e);var n=document.createElement("IFRAME");n.setAttribute("src","connectsdk://"+t),document.documentElement.appendChild(n),n.parentNode.removeChild(n),n=null}},o),i.GoogleCast={name:"Google Cast",interactive:!1,init:function(){var e=this.setMediaElement;this.setMediaElement=function(t){e.apply(this,arguments),this._setCastElement(t)},this._setCastElement(this.mediaElement),window.castReceiverManager=cast.receiver.CastReceiverManager.getInstance(),window.castReceiverManager.addEventListener("ready",this._handleReady.bind(this)),window.castMessageBus=window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk"),window.castMessageBus.addEventListener("message",this.handleMessage.bind(this)),window.castReceiverManager.start()},_handleReady:function(e){window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_CONNECTED,this.handleSenderConnected.bind(this)),window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_DISCONNECTED,this.handleSenderDisconnected.bind(this)),this.handleReady(e)},_setCastElement:function(e){e&&(window.castMediaManager?window.castMediaManager.setMediaElement(e):window.castMediaManager=new cast.receiver.MediaManager(e))},handleMessage:function(e){var t;try{t=JSON.parse(e.data)}catch(n){t=e.data}this.emit(a.EventType.MESSAGE,{from:e.senderId,message:t})},sendMessage:function(e,t){var n;if("string"==typeof t)window.castMessageBus.send(e,t);else{var n=JSON.stringify(t);n&&window.castMessageBus.send(e,n)}},broadcastMessage:function(e){var t;if("string"==typeof e)window.castMessageBus.broadcast(e);else{var t=JSON.stringify(e);t&&window.castMessageBus.broadcast(t)}},handleSenderConnected:function(e){null!=e&&null!=e.senderId&&(e.id=e.senderId,this.emit(a.EventType.JOIN,e))},handleSenderDisconnected:function(e){null!=e&&null!=e.senderId&&(e.id=e.senderId,this.emit(a.EventType.DEPART,e))}};var d,c=e({mixins:[s],constructor:function(){function e(e,t,n){var s=new window.PalmServiceBridge;return s.onservicecallback=function(e){n(JSON.parse(e))},s.call(e,JSON.stringify(t||{})),s}function t(e){for(var t,n=e.split("&"),s={},a=0,i=n.length;i>a;a++)t=n[a].split("="),s[t[0]]=decodeURIComponent(t[1]);return s}if(d)return d;if(d=this,this.stopRequested=!1,this.ws=null,this.channels=[],window.PalmServiceBridge){var n,s="com.webos.service.secondscreen.gateway",a="luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel",i="luna://com.palm.bus/signal/registerServerStatus";this.getAppChannelWebSocket=function(t){statusSubcription=e(i,{subscribe:!0,serviceName:s},function(s){s&&s.connected&&(n=e(a,{},function(e){e.socketUrl&&t(new WebSocket(e.socketUrl))}))})}}else{var o=!1;if(window.location.search){console.log("found params: ",window.location.search);var r=t(window.location.search.substr(1));r.webOSAppChannelSocketUrl&&(console.log("found websocket URL: ",r.webOSAppChannelSocketUrl),o=!0,this.getAppChannelWebSocket=function(e){e(new WebSocket(r.webOSAppChannelSocketUrl))})}if(!o&&window.NetCastCreateAppChannel){var c="_webOSCreateAppChannelCallback";this.getAppChannelWebSocket=function(e){window[c]=function(t){delete window[c],t.socketUrl&&e(new WebSocket(t.socketUrl))},window.NetCastCreateAppChannel("{}",c,!1)}}}},getAppChannelWebSocket:function(){console.error("app channel socket not supported")},start:function(){this.stopRequested=!1;var e=this;!this.ws&&this.getAppChannelWebSocket(function(t){return e.stopRequested?(e.stop(),void 0):(e.ws=t,e.ws.onopen=function(t){console.log("websocket opened"),e.emit("ready",t)},e.ws.onerror=function(e){console.log("websocket error:",e)},e.ws.onmessage=function(t){try{var n=JSON.parse(t.data)}catch(s){return}switch(console.log("got message: "+JSON.stringify(n)),n.type){case"p2p":e._handleP2PMessage(n);break;case"p2p.depart":e._handleP2PDepart(n);break;case"p2p.join":e._handleP2PJoin(n);break;case"p2p.join-request":e._handleP2PJoinRequest(n)}},e.ws.onclose=function(){e.ws=null},void 0)})},stop:function(){this.stopRequested=!0,this.ws&&this.ws.close(),this._destroy()},sendMessage:function(e,t){var n={type:"p2p",to:e,payload:t};this._send(n)},broadcastMessage:function(e){var t={type:"p2p",payload:e};this._send(t)},_send:function(e){this.ws&&e&&(console.log("sending message: ",e),this.ws.send(JSON.stringify(e)))},_destroy:function(){this.ws&&(this.ws=null)},_handleP2PMessage:function(e){var t=e.payload;t&&(console.log("processing message payload "+JSON.stringify(t)),this.emit("message",{from:e.from,message:e.payload}))},_handleP2PJoinRequest:function(e){var t=e.payload;t&&this._send({type:"p2p.join-response",payload:{allowJoin:!0,requestId:t.requestId}})},_handleP2PJoin:function(e){var t=e.client;t&&(console.log("processing client join "+JSON.stringify(t)),this.emit(a.EventType.JOIN,t))},_handleP2PDepart:function(e){var t=e.from;t&&(console.log("processing client departure "+t),this.emit(a.EventType.DEPART,{id:t}))}});return{ConnectManager:a,WebOSAppChannels:c}}(); -------------------------------------------------------------------------------- /sample/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | border: 0; 5 | font-family: 'FontAwesome'; 6 | } 7 | 8 | body { 9 | background-color: black; 10 | } 11 | 12 | .media { 13 | display: none; 14 | position: absolute; 15 | top: 0; 16 | bottom: 0; 17 | width: 100%; 18 | height: 100%; 19 | background-color: black; 20 | } 21 | 22 | #image { 23 | background-position: center center; 24 | background-repeat: no-repeat; 25 | background-size: contain; 26 | } 27 | 28 | #textContainer { 29 | position: absolute; 30 | display: none; 31 | width: 100%; 32 | height: 120px; 33 | bottom: 0; 34 | background-color: black; 35 | opacity: 50%; 36 | text-align: center; 37 | } 38 | 39 | #textMessage { 40 | display: table-cell; 41 | vertical-align: middle; 42 | color: white; 43 | font-size: 30pt; 44 | } 45 | 46 | #splash { 47 | position: absolute; 48 | display: table; 49 | width: 100%; 50 | height: 100%; 51 | text-align: center; 52 | top: 0; 53 | left: 0; 54 | right: 0; 55 | bottom: 0; 56 | background-color: gray; 57 | } 58 | 59 | #splash .fa-smile-o { 60 | display: table-cell; 61 | vertical-align: middle; 62 | color: white; 63 | font-size: 200pt; 64 | } 65 | 66 | #playControls { 67 | position: absolute; 68 | display: none; 69 | width: 100%; 70 | height: 120px; 71 | bottom: 0; 72 | background-color: black; 73 | opacity: 50%; 74 | text-align: center; 75 | } 76 | 77 | .playControl { 78 | display: table-cell; 79 | vertical-align: middle; 80 | color: white; 81 | font-size: 40pt; 82 | } 83 | 84 | #pauseButton { 85 | display: none; 86 | } 87 | 88 | #playBar { 89 | position: absolute; 90 | display: none; 91 | left: 0px; 92 | bottom: 120px; 93 | width: 100%; 94 | height: 5px; 95 | background-color: red; 96 | } 97 | 98 | #playBarProgress { 99 | width: 0px; 100 | height: 5px; 101 | background-color: orange; 102 | } -------------------------------------------------------------------------------- /sample/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font-family: FontAwesome; 17 | font-style: normal; 18 | font-weight: normal; 19 | line-height: 1; 20 | -webkit-font-smoothing: antialiased; 21 | -moz-osx-font-smoothing: grayscale; 22 | } 23 | /* makes the font 33% larger relative to the icon container */ 24 | .fa-lg { 25 | font-size: 1.3333333333333333em; 26 | line-height: 0.75em; 27 | vertical-align: -15%; 28 | } 29 | .fa-2x { 30 | font-size: 2em; 31 | } 32 | .fa-3x { 33 | font-size: 3em; 34 | } 35 | .fa-4x { 36 | font-size: 4em; 37 | } 38 | .fa-5x { 39 | font-size: 5em; 40 | } 41 | .fa-fw { 42 | width: 1.2857142857142858em; 43 | text-align: center; 44 | } 45 | .fa-ul { 46 | padding-left: 0; 47 | margin-left: 2.142857142857143em; 48 | list-style-type: none; 49 | } 50 | .fa-ul > li { 51 | position: relative; 52 | } 53 | .fa-li { 54 | position: absolute; 55 | left: -2.142857142857143em; 56 | width: 2.142857142857143em; 57 | top: 0.14285714285714285em; 58 | text-align: center; 59 | } 60 | .fa-li.fa-lg { 61 | left: -1.8571428571428572em; 62 | } 63 | .fa-border { 64 | padding: .2em .25em .15em; 65 | border: solid 0.08em #eeeeee; 66 | border-radius: .1em; 67 | } 68 | .pull-right { 69 | float: right; 70 | } 71 | .pull-left { 72 | float: left; 73 | } 74 | .fa.pull-left { 75 | margin-right: .3em; 76 | } 77 | .fa.pull-right { 78 | margin-left: .3em; 79 | } 80 | .fa-spin { 81 | -webkit-animation: spin 2s infinite linear; 82 | -moz-animation: spin 2s infinite linear; 83 | -o-animation: spin 2s infinite linear; 84 | animation: spin 2s infinite linear; 85 | } 86 | @-moz-keyframes spin { 87 | 0% { 88 | -moz-transform: rotate(0deg); 89 | } 90 | 100% { 91 | -moz-transform: rotate(359deg); 92 | } 93 | } 94 | @-webkit-keyframes spin { 95 | 0% { 96 | -webkit-transform: rotate(0deg); 97 | } 98 | 100% { 99 | -webkit-transform: rotate(359deg); 100 | } 101 | } 102 | @-o-keyframes spin { 103 | 0% { 104 | -o-transform: rotate(0deg); 105 | } 106 | 100% { 107 | -o-transform: rotate(359deg); 108 | } 109 | } 110 | @-ms-keyframes spin { 111 | 0% { 112 | -ms-transform: rotate(0deg); 113 | } 114 | 100% { 115 | -ms-transform: rotate(359deg); 116 | } 117 | } 118 | @keyframes spin { 119 | 0% { 120 | transform: rotate(0deg); 121 | } 122 | 100% { 123 | transform: rotate(359deg); 124 | } 125 | } 126 | .fa-rotate-90 { 127 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 128 | -webkit-transform: rotate(90deg); 129 | -moz-transform: rotate(90deg); 130 | -ms-transform: rotate(90deg); 131 | -o-transform: rotate(90deg); 132 | transform: rotate(90deg); 133 | } 134 | .fa-rotate-180 { 135 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 136 | -webkit-transform: rotate(180deg); 137 | -moz-transform: rotate(180deg); 138 | -ms-transform: rotate(180deg); 139 | -o-transform: rotate(180deg); 140 | transform: rotate(180deg); 141 | } 142 | .fa-rotate-270 { 143 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 144 | -webkit-transform: rotate(270deg); 145 | -moz-transform: rotate(270deg); 146 | -ms-transform: rotate(270deg); 147 | -o-transform: rotate(270deg); 148 | transform: rotate(270deg); 149 | } 150 | .fa-flip-horizontal { 151 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 152 | -webkit-transform: scale(-1, 1); 153 | -moz-transform: scale(-1, 1); 154 | -ms-transform: scale(-1, 1); 155 | -o-transform: scale(-1, 1); 156 | transform: scale(-1, 1); 157 | } 158 | .fa-flip-vertical { 159 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 160 | -webkit-transform: scale(1, -1); 161 | -moz-transform: scale(1, -1); 162 | -ms-transform: scale(1, -1); 163 | -o-transform: scale(1, -1); 164 | transform: scale(1, -1); 165 | } 166 | .fa-stack { 167 | position: relative; 168 | display: inline-block; 169 | width: 2em; 170 | height: 2em; 171 | line-height: 2em; 172 | vertical-align: middle; 173 | } 174 | .fa-stack-1x, 175 | .fa-stack-2x { 176 | position: absolute; 177 | left: 0; 178 | width: 100%; 179 | text-align: center; 180 | } 181 | .fa-stack-1x { 182 | line-height: inherit; 183 | } 184 | .fa-stack-2x { 185 | font-size: 2em; 186 | } 187 | .fa-inverse { 188 | color: #ffffff; 189 | } 190 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 191 | readers do not read off random characters that represent icons */ 192 | .fa-glass:before { 193 | content: "\f000"; 194 | } 195 | .fa-music:before { 196 | content: "\f001"; 197 | } 198 | .fa-search:before { 199 | content: "\f002"; 200 | } 201 | .fa-envelope-o:before { 202 | content: "\f003"; 203 | } 204 | .fa-heart:before { 205 | content: "\f004"; 206 | } 207 | .fa-star:before { 208 | content: "\f005"; 209 | } 210 | .fa-star-o:before { 211 | content: "\f006"; 212 | } 213 | .fa-user:before { 214 | content: "\f007"; 215 | } 216 | .fa-film:before { 217 | content: "\f008"; 218 | } 219 | .fa-th-large:before { 220 | content: "\f009"; 221 | } 222 | .fa-th:before { 223 | content: "\f00a"; 224 | } 225 | .fa-th-list:before { 226 | content: "\f00b"; 227 | } 228 | .fa-check:before { 229 | content: "\f00c"; 230 | } 231 | .fa-times:before { 232 | content: "\f00d"; 233 | } 234 | .fa-search-plus:before { 235 | content: "\f00e"; 236 | } 237 | .fa-search-minus:before { 238 | content: "\f010"; 239 | } 240 | .fa-power-off:before { 241 | content: "\f011"; 242 | } 243 | .fa-signal:before { 244 | content: "\f012"; 245 | } 246 | .fa-gear:before, 247 | .fa-cog:before { 248 | content: "\f013"; 249 | } 250 | .fa-trash-o:before { 251 | content: "\f014"; 252 | } 253 | .fa-home:before { 254 | content: "\f015"; 255 | } 256 | .fa-file-o:before { 257 | content: "\f016"; 258 | } 259 | .fa-clock-o:before { 260 | content: "\f017"; 261 | } 262 | .fa-road:before { 263 | content: "\f018"; 264 | } 265 | .fa-download:before { 266 | content: "\f019"; 267 | } 268 | .fa-arrow-circle-o-down:before { 269 | content: "\f01a"; 270 | } 271 | .fa-arrow-circle-o-up:before { 272 | content: "\f01b"; 273 | } 274 | .fa-inbox:before { 275 | content: "\f01c"; 276 | } 277 | .fa-play-circle-o:before { 278 | content: "\f01d"; 279 | } 280 | .fa-rotate-right:before, 281 | .fa-repeat:before { 282 | content: "\f01e"; 283 | } 284 | .fa-refresh:before { 285 | content: "\f021"; 286 | } 287 | .fa-list-alt:before { 288 | content: "\f022"; 289 | } 290 | .fa-lock:before { 291 | content: "\f023"; 292 | } 293 | .fa-flag:before { 294 | content: "\f024"; 295 | } 296 | .fa-headphones:before { 297 | content: "\f025"; 298 | } 299 | .fa-volume-off:before { 300 | content: "\f026"; 301 | } 302 | .fa-volume-down:before { 303 | content: "\f027"; 304 | } 305 | .fa-volume-up:before { 306 | content: "\f028"; 307 | } 308 | .fa-qrcode:before { 309 | content: "\f029"; 310 | } 311 | .fa-barcode:before { 312 | content: "\f02a"; 313 | } 314 | .fa-tag:before { 315 | content: "\f02b"; 316 | } 317 | .fa-tags:before { 318 | content: "\f02c"; 319 | } 320 | .fa-book:before { 321 | content: "\f02d"; 322 | } 323 | .fa-bookmark:before { 324 | content: "\f02e"; 325 | } 326 | .fa-print:before { 327 | content: "\f02f"; 328 | } 329 | .fa-camera:before { 330 | content: "\f030"; 331 | } 332 | .fa-font:before { 333 | content: "\f031"; 334 | } 335 | .fa-bold:before { 336 | content: "\f032"; 337 | } 338 | .fa-italic:before { 339 | content: "\f033"; 340 | } 341 | .fa-text-height:before { 342 | content: "\f034"; 343 | } 344 | .fa-text-width:before { 345 | content: "\f035"; 346 | } 347 | .fa-align-left:before { 348 | content: "\f036"; 349 | } 350 | .fa-align-center:before { 351 | content: "\f037"; 352 | } 353 | .fa-align-right:before { 354 | content: "\f038"; 355 | } 356 | .fa-align-justify:before { 357 | content: "\f039"; 358 | } 359 | .fa-list:before { 360 | content: "\f03a"; 361 | } 362 | .fa-dedent:before, 363 | .fa-outdent:before { 364 | content: "\f03b"; 365 | } 366 | .fa-indent:before { 367 | content: "\f03c"; 368 | } 369 | .fa-video-camera:before { 370 | content: "\f03d"; 371 | } 372 | .fa-picture-o:before { 373 | content: "\f03e"; 374 | } 375 | .fa-pencil:before { 376 | content: "\f040"; 377 | } 378 | .fa-map-marker:before { 379 | content: "\f041"; 380 | } 381 | .fa-adjust:before { 382 | content: "\f042"; 383 | } 384 | .fa-tint:before { 385 | content: "\f043"; 386 | } 387 | .fa-edit:before, 388 | .fa-pencil-square-o:before { 389 | content: "\f044"; 390 | } 391 | .fa-share-square-o:before { 392 | content: "\f045"; 393 | } 394 | .fa-check-square-o:before { 395 | content: "\f046"; 396 | } 397 | .fa-arrows:before { 398 | content: "\f047"; 399 | } 400 | .fa-step-backward:before { 401 | content: "\f048"; 402 | } 403 | .fa-fast-backward:before { 404 | content: "\f049"; 405 | } 406 | .fa-backward:before { 407 | content: "\f04a"; 408 | } 409 | .fa-play:before { 410 | content: "\f04b"; 411 | } 412 | .fa-pause:before { 413 | content: "\f04c"; 414 | } 415 | .fa-stop:before { 416 | content: "\f04d"; 417 | } 418 | .fa-forward:before { 419 | content: "\f04e"; 420 | } 421 | .fa-fast-forward:before { 422 | content: "\f050"; 423 | } 424 | .fa-step-forward:before { 425 | content: "\f051"; 426 | } 427 | .fa-eject:before { 428 | content: "\f052"; 429 | } 430 | .fa-chevron-left:before { 431 | content: "\f053"; 432 | } 433 | .fa-chevron-right:before { 434 | content: "\f054"; 435 | } 436 | .fa-plus-circle:before { 437 | content: "\f055"; 438 | } 439 | .fa-minus-circle:before { 440 | content: "\f056"; 441 | } 442 | .fa-times-circle:before { 443 | content: "\f057"; 444 | } 445 | .fa-check-circle:before { 446 | content: "\f058"; 447 | } 448 | .fa-question-circle:before { 449 | content: "\f059"; 450 | } 451 | .fa-info-circle:before { 452 | content: "\f05a"; 453 | } 454 | .fa-crosshairs:before { 455 | content: "\f05b"; 456 | } 457 | .fa-times-circle-o:before { 458 | content: "\f05c"; 459 | } 460 | .fa-check-circle-o:before { 461 | content: "\f05d"; 462 | } 463 | .fa-ban:before { 464 | content: "\f05e"; 465 | } 466 | .fa-arrow-left:before { 467 | content: "\f060"; 468 | } 469 | .fa-arrow-right:before { 470 | content: "\f061"; 471 | } 472 | .fa-arrow-up:before { 473 | content: "\f062"; 474 | } 475 | .fa-arrow-down:before { 476 | content: "\f063"; 477 | } 478 | .fa-mail-forward:before, 479 | .fa-share:before { 480 | content: "\f064"; 481 | } 482 | .fa-expand:before { 483 | content: "\f065"; 484 | } 485 | .fa-compress:before { 486 | content: "\f066"; 487 | } 488 | .fa-plus:before { 489 | content: "\f067"; 490 | } 491 | .fa-minus:before { 492 | content: "\f068"; 493 | } 494 | .fa-asterisk:before { 495 | content: "\f069"; 496 | } 497 | .fa-exclamation-circle:before { 498 | content: "\f06a"; 499 | } 500 | .fa-gift:before { 501 | content: "\f06b"; 502 | } 503 | .fa-leaf:before { 504 | content: "\f06c"; 505 | } 506 | .fa-fire:before { 507 | content: "\f06d"; 508 | } 509 | .fa-eye:before { 510 | content: "\f06e"; 511 | } 512 | .fa-eye-slash:before { 513 | content: "\f070"; 514 | } 515 | .fa-warning:before, 516 | .fa-exclamation-triangle:before { 517 | content: "\f071"; 518 | } 519 | .fa-plane:before { 520 | content: "\f072"; 521 | } 522 | .fa-calendar:before { 523 | content: "\f073"; 524 | } 525 | .fa-random:before { 526 | content: "\f074"; 527 | } 528 | .fa-comment:before { 529 | content: "\f075"; 530 | } 531 | .fa-magnet:before { 532 | content: "\f076"; 533 | } 534 | .fa-chevron-up:before { 535 | content: "\f077"; 536 | } 537 | .fa-chevron-down:before { 538 | content: "\f078"; 539 | } 540 | .fa-retweet:before { 541 | content: "\f079"; 542 | } 543 | .fa-shopping-cart:before { 544 | content: "\f07a"; 545 | } 546 | .fa-folder:before { 547 | content: "\f07b"; 548 | } 549 | .fa-folder-open:before { 550 | content: "\f07c"; 551 | } 552 | .fa-arrows-v:before { 553 | content: "\f07d"; 554 | } 555 | .fa-arrows-h:before { 556 | content: "\f07e"; 557 | } 558 | .fa-bar-chart-o:before { 559 | content: "\f080"; 560 | } 561 | .fa-twitter-square:before { 562 | content: "\f081"; 563 | } 564 | .fa-facebook-square:before { 565 | content: "\f082"; 566 | } 567 | .fa-camera-retro:before { 568 | content: "\f083"; 569 | } 570 | .fa-key:before { 571 | content: "\f084"; 572 | } 573 | .fa-gears:before, 574 | .fa-cogs:before { 575 | content: "\f085"; 576 | } 577 | .fa-comments:before { 578 | content: "\f086"; 579 | } 580 | .fa-thumbs-o-up:before { 581 | content: "\f087"; 582 | } 583 | .fa-thumbs-o-down:before { 584 | content: "\f088"; 585 | } 586 | .fa-star-half:before { 587 | content: "\f089"; 588 | } 589 | .fa-heart-o:before { 590 | content: "\f08a"; 591 | } 592 | .fa-sign-out:before { 593 | content: "\f08b"; 594 | } 595 | .fa-linkedin-square:before { 596 | content: "\f08c"; 597 | } 598 | .fa-thumb-tack:before { 599 | content: "\f08d"; 600 | } 601 | .fa-external-link:before { 602 | content: "\f08e"; 603 | } 604 | .fa-sign-in:before { 605 | content: "\f090"; 606 | } 607 | .fa-trophy:before { 608 | content: "\f091"; 609 | } 610 | .fa-github-square:before { 611 | content: "\f092"; 612 | } 613 | .fa-upload:before { 614 | content: "\f093"; 615 | } 616 | .fa-lemon-o:before { 617 | content: "\f094"; 618 | } 619 | .fa-phone:before { 620 | content: "\f095"; 621 | } 622 | .fa-square-o:before { 623 | content: "\f096"; 624 | } 625 | .fa-bookmark-o:before { 626 | content: "\f097"; 627 | } 628 | .fa-phone-square:before { 629 | content: "\f098"; 630 | } 631 | .fa-twitter:before { 632 | content: "\f099"; 633 | } 634 | .fa-facebook:before { 635 | content: "\f09a"; 636 | } 637 | .fa-github:before { 638 | content: "\f09b"; 639 | } 640 | .fa-unlock:before { 641 | content: "\f09c"; 642 | } 643 | .fa-credit-card:before { 644 | content: "\f09d"; 645 | } 646 | .fa-rss:before { 647 | content: "\f09e"; 648 | } 649 | .fa-hdd-o:before { 650 | content: "\f0a0"; 651 | } 652 | .fa-bullhorn:before { 653 | content: "\f0a1"; 654 | } 655 | .fa-bell:before { 656 | content: "\f0f3"; 657 | } 658 | .fa-certificate:before { 659 | content: "\f0a3"; 660 | } 661 | .fa-hand-o-right:before { 662 | content: "\f0a4"; 663 | } 664 | .fa-hand-o-left:before { 665 | content: "\f0a5"; 666 | } 667 | .fa-hand-o-up:before { 668 | content: "\f0a6"; 669 | } 670 | .fa-hand-o-down:before { 671 | content: "\f0a7"; 672 | } 673 | .fa-arrow-circle-left:before { 674 | content: "\f0a8"; 675 | } 676 | .fa-arrow-circle-right:before { 677 | content: "\f0a9"; 678 | } 679 | .fa-arrow-circle-up:before { 680 | content: "\f0aa"; 681 | } 682 | .fa-arrow-circle-down:before { 683 | content: "\f0ab"; 684 | } 685 | .fa-globe:before { 686 | content: "\f0ac"; 687 | } 688 | .fa-wrench:before { 689 | content: "\f0ad"; 690 | } 691 | .fa-tasks:before { 692 | content: "\f0ae"; 693 | } 694 | .fa-filter:before { 695 | content: "\f0b0"; 696 | } 697 | .fa-briefcase:before { 698 | content: "\f0b1"; 699 | } 700 | .fa-arrows-alt:before { 701 | content: "\f0b2"; 702 | } 703 | .fa-group:before, 704 | .fa-users:before { 705 | content: "\f0c0"; 706 | } 707 | .fa-chain:before, 708 | .fa-link:before { 709 | content: "\f0c1"; 710 | } 711 | .fa-cloud:before { 712 | content: "\f0c2"; 713 | } 714 | .fa-flask:before { 715 | content: "\f0c3"; 716 | } 717 | .fa-cut:before, 718 | .fa-scissors:before { 719 | content: "\f0c4"; 720 | } 721 | .fa-copy:before, 722 | .fa-files-o:before { 723 | content: "\f0c5"; 724 | } 725 | .fa-paperclip:before { 726 | content: "\f0c6"; 727 | } 728 | .fa-save:before, 729 | .fa-floppy-o:before { 730 | content: "\f0c7"; 731 | } 732 | .fa-square:before { 733 | content: "\f0c8"; 734 | } 735 | .fa-bars:before { 736 | content: "\f0c9"; 737 | } 738 | .fa-list-ul:before { 739 | content: "\f0ca"; 740 | } 741 | .fa-list-ol:before { 742 | content: "\f0cb"; 743 | } 744 | .fa-strikethrough:before { 745 | content: "\f0cc"; 746 | } 747 | .fa-underline:before { 748 | content: "\f0cd"; 749 | } 750 | .fa-table:before { 751 | content: "\f0ce"; 752 | } 753 | .fa-magic:before { 754 | content: "\f0d0"; 755 | } 756 | .fa-truck:before { 757 | content: "\f0d1"; 758 | } 759 | .fa-pinterest:before { 760 | content: "\f0d2"; 761 | } 762 | .fa-pinterest-square:before { 763 | content: "\f0d3"; 764 | } 765 | .fa-google-plus-square:before { 766 | content: "\f0d4"; 767 | } 768 | .fa-google-plus:before { 769 | content: "\f0d5"; 770 | } 771 | .fa-money:before { 772 | content: "\f0d6"; 773 | } 774 | .fa-caret-down:before { 775 | content: "\f0d7"; 776 | } 777 | .fa-caret-up:before { 778 | content: "\f0d8"; 779 | } 780 | .fa-caret-left:before { 781 | content: "\f0d9"; 782 | } 783 | .fa-caret-right:before { 784 | content: "\f0da"; 785 | } 786 | .fa-columns:before { 787 | content: "\f0db"; 788 | } 789 | .fa-unsorted:before, 790 | .fa-sort:before { 791 | content: "\f0dc"; 792 | } 793 | .fa-sort-down:before, 794 | .fa-sort-asc:before { 795 | content: "\f0dd"; 796 | } 797 | .fa-sort-up:before, 798 | .fa-sort-desc:before { 799 | content: "\f0de"; 800 | } 801 | .fa-envelope:before { 802 | content: "\f0e0"; 803 | } 804 | .fa-linkedin:before { 805 | content: "\f0e1"; 806 | } 807 | .fa-rotate-left:before, 808 | .fa-undo:before { 809 | content: "\f0e2"; 810 | } 811 | .fa-legal:before, 812 | .fa-gavel:before { 813 | content: "\f0e3"; 814 | } 815 | .fa-dashboard:before, 816 | .fa-tachometer:before { 817 | content: "\f0e4"; 818 | } 819 | .fa-comment-o:before { 820 | content: "\f0e5"; 821 | } 822 | .fa-comments-o:before { 823 | content: "\f0e6"; 824 | } 825 | .fa-flash:before, 826 | .fa-bolt:before { 827 | content: "\f0e7"; 828 | } 829 | .fa-sitemap:before { 830 | content: "\f0e8"; 831 | } 832 | .fa-umbrella:before { 833 | content: "\f0e9"; 834 | } 835 | .fa-paste:before, 836 | .fa-clipboard:before { 837 | content: "\f0ea"; 838 | } 839 | .fa-lightbulb-o:before { 840 | content: "\f0eb"; 841 | } 842 | .fa-exchange:before { 843 | content: "\f0ec"; 844 | } 845 | .fa-cloud-download:before { 846 | content: "\f0ed"; 847 | } 848 | .fa-cloud-upload:before { 849 | content: "\f0ee"; 850 | } 851 | .fa-user-md:before { 852 | content: "\f0f0"; 853 | } 854 | .fa-stethoscope:before { 855 | content: "\f0f1"; 856 | } 857 | .fa-suitcase:before { 858 | content: "\f0f2"; 859 | } 860 | .fa-bell-o:before { 861 | content: "\f0a2"; 862 | } 863 | .fa-coffee:before { 864 | content: "\f0f4"; 865 | } 866 | .fa-cutlery:before { 867 | content: "\f0f5"; 868 | } 869 | .fa-file-text-o:before { 870 | content: "\f0f6"; 871 | } 872 | .fa-building-o:before { 873 | content: "\f0f7"; 874 | } 875 | .fa-hospital-o:before { 876 | content: "\f0f8"; 877 | } 878 | .fa-ambulance:before { 879 | content: "\f0f9"; 880 | } 881 | .fa-medkit:before { 882 | content: "\f0fa"; 883 | } 884 | .fa-fighter-jet:before { 885 | content: "\f0fb"; 886 | } 887 | .fa-beer:before { 888 | content: "\f0fc"; 889 | } 890 | .fa-h-square:before { 891 | content: "\f0fd"; 892 | } 893 | .fa-plus-square:before { 894 | content: "\f0fe"; 895 | } 896 | .fa-angle-double-left:before { 897 | content: "\f100"; 898 | } 899 | .fa-angle-double-right:before { 900 | content: "\f101"; 901 | } 902 | .fa-angle-double-up:before { 903 | content: "\f102"; 904 | } 905 | .fa-angle-double-down:before { 906 | content: "\f103"; 907 | } 908 | .fa-angle-left:before { 909 | content: "\f104"; 910 | } 911 | .fa-angle-right:before { 912 | content: "\f105"; 913 | } 914 | .fa-angle-up:before { 915 | content: "\f106"; 916 | } 917 | .fa-angle-down:before { 918 | content: "\f107"; 919 | } 920 | .fa-desktop:before { 921 | content: "\f108"; 922 | } 923 | .fa-laptop:before { 924 | content: "\f109"; 925 | } 926 | .fa-tablet:before { 927 | content: "\f10a"; 928 | } 929 | .fa-mobile-phone:before, 930 | .fa-mobile:before { 931 | content: "\f10b"; 932 | } 933 | .fa-circle-o:before { 934 | content: "\f10c"; 935 | } 936 | .fa-quote-left:before { 937 | content: "\f10d"; 938 | } 939 | .fa-quote-right:before { 940 | content: "\f10e"; 941 | } 942 | .fa-spinner:before { 943 | content: "\f110"; 944 | } 945 | .fa-circle:before { 946 | content: "\f111"; 947 | } 948 | .fa-mail-reply:before, 949 | .fa-reply:before { 950 | content: "\f112"; 951 | } 952 | .fa-github-alt:before { 953 | content: "\f113"; 954 | } 955 | .fa-folder-o:before { 956 | content: "\f114"; 957 | } 958 | .fa-folder-open-o:before { 959 | content: "\f115"; 960 | } 961 | .fa-smile-o:before { 962 | content: "\f118"; 963 | } 964 | .fa-frown-o:before { 965 | content: "\f119"; 966 | } 967 | .fa-meh-o:before { 968 | content: "\f11a"; 969 | } 970 | .fa-gamepad:before { 971 | content: "\f11b"; 972 | } 973 | .fa-keyboard-o:before { 974 | content: "\f11c"; 975 | } 976 | .fa-flag-o:before { 977 | content: "\f11d"; 978 | } 979 | .fa-flag-checkered:before { 980 | content: "\f11e"; 981 | } 982 | .fa-terminal:before { 983 | content: "\f120"; 984 | } 985 | .fa-code:before { 986 | content: "\f121"; 987 | } 988 | .fa-reply-all:before { 989 | content: "\f122"; 990 | } 991 | .fa-mail-reply-all:before { 992 | content: "\f122"; 993 | } 994 | .fa-star-half-empty:before, 995 | .fa-star-half-full:before, 996 | .fa-star-half-o:before { 997 | content: "\f123"; 998 | } 999 | .fa-location-arrow:before { 1000 | content: "\f124"; 1001 | } 1002 | .fa-crop:before { 1003 | content: "\f125"; 1004 | } 1005 | .fa-code-fork:before { 1006 | content: "\f126"; 1007 | } 1008 | .fa-unlink:before, 1009 | .fa-chain-broken:before { 1010 | content: "\f127"; 1011 | } 1012 | .fa-question:before { 1013 | content: "\f128"; 1014 | } 1015 | .fa-info:before { 1016 | content: "\f129"; 1017 | } 1018 | .fa-exclamation:before { 1019 | content: "\f12a"; 1020 | } 1021 | .fa-superscript:before { 1022 | content: "\f12b"; 1023 | } 1024 | .fa-subscript:before { 1025 | content: "\f12c"; 1026 | } 1027 | .fa-eraser:before { 1028 | content: "\f12d"; 1029 | } 1030 | .fa-puzzle-piece:before { 1031 | content: "\f12e"; 1032 | } 1033 | .fa-microphone:before { 1034 | content: "\f130"; 1035 | } 1036 | .fa-microphone-slash:before { 1037 | content: "\f131"; 1038 | } 1039 | .fa-shield:before { 1040 | content: "\f132"; 1041 | } 1042 | .fa-calendar-o:before { 1043 | content: "\f133"; 1044 | } 1045 | .fa-fire-extinguisher:before { 1046 | content: "\f134"; 1047 | } 1048 | .fa-rocket:before { 1049 | content: "\f135"; 1050 | } 1051 | .fa-maxcdn:before { 1052 | content: "\f136"; 1053 | } 1054 | .fa-chevron-circle-left:before { 1055 | content: "\f137"; 1056 | } 1057 | .fa-chevron-circle-right:before { 1058 | content: "\f138"; 1059 | } 1060 | .fa-chevron-circle-up:before { 1061 | content: "\f139"; 1062 | } 1063 | .fa-chevron-circle-down:before { 1064 | content: "\f13a"; 1065 | } 1066 | .fa-html5:before { 1067 | content: "\f13b"; 1068 | } 1069 | .fa-css3:before { 1070 | content: "\f13c"; 1071 | } 1072 | .fa-anchor:before { 1073 | content: "\f13d"; 1074 | } 1075 | .fa-unlock-alt:before { 1076 | content: "\f13e"; 1077 | } 1078 | .fa-bullseye:before { 1079 | content: "\f140"; 1080 | } 1081 | .fa-ellipsis-h:before { 1082 | content: "\f141"; 1083 | } 1084 | .fa-ellipsis-v:before { 1085 | content: "\f142"; 1086 | } 1087 | .fa-rss-square:before { 1088 | content: "\f143"; 1089 | } 1090 | .fa-play-circle:before { 1091 | content: "\f144"; 1092 | } 1093 | .fa-ticket:before { 1094 | content: "\f145"; 1095 | } 1096 | .fa-minus-square:before { 1097 | content: "\f146"; 1098 | } 1099 | .fa-minus-square-o:before { 1100 | content: "\f147"; 1101 | } 1102 | .fa-level-up:before { 1103 | content: "\f148"; 1104 | } 1105 | .fa-level-down:before { 1106 | content: "\f149"; 1107 | } 1108 | .fa-check-square:before { 1109 | content: "\f14a"; 1110 | } 1111 | .fa-pencil-square:before { 1112 | content: "\f14b"; 1113 | } 1114 | .fa-external-link-square:before { 1115 | content: "\f14c"; 1116 | } 1117 | .fa-share-square:before { 1118 | content: "\f14d"; 1119 | } 1120 | .fa-compass:before { 1121 | content: "\f14e"; 1122 | } 1123 | .fa-toggle-down:before, 1124 | .fa-caret-square-o-down:before { 1125 | content: "\f150"; 1126 | } 1127 | .fa-toggle-up:before, 1128 | .fa-caret-square-o-up:before { 1129 | content: "\f151"; 1130 | } 1131 | .fa-toggle-right:before, 1132 | .fa-caret-square-o-right:before { 1133 | content: "\f152"; 1134 | } 1135 | .fa-euro:before, 1136 | .fa-eur:before { 1137 | content: "\f153"; 1138 | } 1139 | .fa-gbp:before { 1140 | content: "\f154"; 1141 | } 1142 | .fa-dollar:before, 1143 | .fa-usd:before { 1144 | content: "\f155"; 1145 | } 1146 | .fa-rupee:before, 1147 | .fa-inr:before { 1148 | content: "\f156"; 1149 | } 1150 | .fa-cny:before, 1151 | .fa-rmb:before, 1152 | .fa-yen:before, 1153 | .fa-jpy:before { 1154 | content: "\f157"; 1155 | } 1156 | .fa-ruble:before, 1157 | .fa-rouble:before, 1158 | .fa-rub:before { 1159 | content: "\f158"; 1160 | } 1161 | .fa-won:before, 1162 | .fa-krw:before { 1163 | content: "\f159"; 1164 | } 1165 | .fa-bitcoin:before, 1166 | .fa-btc:before { 1167 | content: "\f15a"; 1168 | } 1169 | .fa-file:before { 1170 | content: "\f15b"; 1171 | } 1172 | .fa-file-text:before { 1173 | content: "\f15c"; 1174 | } 1175 | .fa-sort-alpha-asc:before { 1176 | content: "\f15d"; 1177 | } 1178 | .fa-sort-alpha-desc:before { 1179 | content: "\f15e"; 1180 | } 1181 | .fa-sort-amount-asc:before { 1182 | content: "\f160"; 1183 | } 1184 | .fa-sort-amount-desc:before { 1185 | content: "\f161"; 1186 | } 1187 | .fa-sort-numeric-asc:before { 1188 | content: "\f162"; 1189 | } 1190 | .fa-sort-numeric-desc:before { 1191 | content: "\f163"; 1192 | } 1193 | .fa-thumbs-up:before { 1194 | content: "\f164"; 1195 | } 1196 | .fa-thumbs-down:before { 1197 | content: "\f165"; 1198 | } 1199 | .fa-youtube-square:before { 1200 | content: "\f166"; 1201 | } 1202 | .fa-youtube:before { 1203 | content: "\f167"; 1204 | } 1205 | .fa-xing:before { 1206 | content: "\f168"; 1207 | } 1208 | .fa-xing-square:before { 1209 | content: "\f169"; 1210 | } 1211 | .fa-youtube-play:before { 1212 | content: "\f16a"; 1213 | } 1214 | .fa-dropbox:before { 1215 | content: "\f16b"; 1216 | } 1217 | .fa-stack-overflow:before { 1218 | content: "\f16c"; 1219 | } 1220 | .fa-instagram:before { 1221 | content: "\f16d"; 1222 | } 1223 | .fa-flickr:before { 1224 | content: "\f16e"; 1225 | } 1226 | .fa-adn:before { 1227 | content: "\f170"; 1228 | } 1229 | .fa-bitbucket:before { 1230 | content: "\f171"; 1231 | } 1232 | .fa-bitbucket-square:before { 1233 | content: "\f172"; 1234 | } 1235 | .fa-tumblr:before { 1236 | content: "\f173"; 1237 | } 1238 | .fa-tumblr-square:before { 1239 | content: "\f174"; 1240 | } 1241 | .fa-long-arrow-down:before { 1242 | content: "\f175"; 1243 | } 1244 | .fa-long-arrow-up:before { 1245 | content: "\f176"; 1246 | } 1247 | .fa-long-arrow-left:before { 1248 | content: "\f177"; 1249 | } 1250 | .fa-long-arrow-right:before { 1251 | content: "\f178"; 1252 | } 1253 | .fa-apple:before { 1254 | content: "\f179"; 1255 | } 1256 | .fa-windows:before { 1257 | content: "\f17a"; 1258 | } 1259 | .fa-android:before { 1260 | content: "\f17b"; 1261 | } 1262 | .fa-linux:before { 1263 | content: "\f17c"; 1264 | } 1265 | .fa-dribbble:before { 1266 | content: "\f17d"; 1267 | } 1268 | .fa-skype:before { 1269 | content: "\f17e"; 1270 | } 1271 | .fa-foursquare:before { 1272 | content: "\f180"; 1273 | } 1274 | .fa-trello:before { 1275 | content: "\f181"; 1276 | } 1277 | .fa-female:before { 1278 | content: "\f182"; 1279 | } 1280 | .fa-male:before { 1281 | content: "\f183"; 1282 | } 1283 | .fa-gittip:before { 1284 | content: "\f184"; 1285 | } 1286 | .fa-sun-o:before { 1287 | content: "\f185"; 1288 | } 1289 | .fa-moon-o:before { 1290 | content: "\f186"; 1291 | } 1292 | .fa-archive:before { 1293 | content: "\f187"; 1294 | } 1295 | .fa-bug:before { 1296 | content: "\f188"; 1297 | } 1298 | .fa-vk:before { 1299 | content: "\f189"; 1300 | } 1301 | .fa-weibo:before { 1302 | content: "\f18a"; 1303 | } 1304 | .fa-renren:before { 1305 | content: "\f18b"; 1306 | } 1307 | .fa-pagelines:before { 1308 | content: "\f18c"; 1309 | } 1310 | .fa-stack-exchange:before { 1311 | content: "\f18d"; 1312 | } 1313 | .fa-arrow-circle-o-right:before { 1314 | content: "\f18e"; 1315 | } 1316 | .fa-arrow-circle-o-left:before { 1317 | content: "\f190"; 1318 | } 1319 | .fa-toggle-left:before, 1320 | .fa-caret-square-o-left:before { 1321 | content: "\f191"; 1322 | } 1323 | .fa-dot-circle-o:before { 1324 | content: "\f192"; 1325 | } 1326 | .fa-wheelchair:before { 1327 | content: "\f193"; 1328 | } 1329 | .fa-vimeo-square:before { 1330 | content: "\f194"; 1331 | } 1332 | .fa-turkish-lira:before, 1333 | .fa-try:before { 1334 | content: "\f195"; 1335 | } 1336 | .fa-plus-square-o:before { 1337 | content: "\f196"; 1338 | } 1339 | -------------------------------------------------------------------------------- /sample/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-JavaScript-Bridge/9b343af357982b8a93bb5a1f643cea36308f5b7e/sample/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /sample/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-JavaScript-Bridge/9b343af357982b8a93bb5a1f643cea36308f5b7e/sample/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /sample/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-JavaScript-Bridge/9b343af357982b8a93bb5a1f643cea36308f5b7e/sample/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /sample/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-JavaScript-Bridge/9b343af357982b8a93bb5a1f643cea36308f5b7e/sample/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Connect SDK Test App 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 |
App is loading...
24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 |
00:00
32 | 33 | 34 |
00:00
35 |
36 | 37 | 38 | 39 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /sample/scripts/app.js: -------------------------------------------------------------------------------- 1 | String.prototype.toHHMMSS = function () { 2 | var sec_num = parseInt(this, 10); // don't forget the second param 3 | var hours = Math.floor(sec_num / 3600); 4 | var minutes = Math.floor((sec_num - (hours * 3600)) / 60); 5 | var seconds = sec_num - (hours * 3600) - (minutes * 60); 6 | 7 | if (hours < 10) {hours = "0"+hours;} 8 | if (minutes < 10) {minutes = "0"+minutes;} 9 | if (seconds < 10) {seconds = "0"+seconds;} 10 | var time; 11 | 12 | if (hours > 0) 13 | time = hours+':'+minutes+':'+seconds; 14 | else 15 | time = minutes + ':' + seconds; 16 | 17 | return time; 18 | } 19 | 20 | function getParameterByName(name) { 21 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 22 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 23 | results = regex.exec(location.search); 24 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 25 | } -------------------------------------------------------------------------------- /sample/scripts/connect_bridge.min.js: -------------------------------------------------------------------------------- 1 | // 2 | // Connect SDK JavaScript Bridge 3 | // Version 1.3.1 Date: 11 Jun 2014 2:22 PM 4 | // 5 | // Created by Jeremy White on 4/16/14. 6 | // Copyright (c) 2014 LG Electronics. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | var connectsdk=function(){function e(e){var n;if(!e.constructor)throw n=function(){},Error("no constructor");n=e.constructor,delete e.constructor;var s=n.prototype;return e.mixins&&(e.mixins.forEach(function(e){t(s,e)}),delete e.mixins),e.statics&&(t(n,e.statics),delete e.statics),t(s,e),n}function t(e,t){for(var n in t)t.hasOwnProperty(n)&&!e.hasOwnProperty(n)&&(e[n]=t[n]);return e}function n(){}var s={addListener:function(e,t,n){if(!e)throw Error("missing parameter: event");if(!t)throw Error("missing parameter: callback");return this._listeners=this._listeners||{},this._listeners||(this._listeners={}),this._listeners[e]||(this._listeners[e]=[]),this._listeners[e].push({callback:t,context:n}),this.emit("_addListener",e),this},removeListener:function(e,t,n){return this._listeners&&this._listeners[e]&&(this._listeners[e]=this._listeners[e].filter(function(e){return t&&t!==e.callback&&n&&n!==e.context})),this.emit("_removeListener",e),this},hasListeners:function(e){if(e)return this._listeners&&this._listeners[e]&&this._listeners[e].length>0;for(e in this._listeners)if("_"!==e[0]&&this._listeners.hasOwnProperty(e)&&this._listeners[e].length>0)return!0;return!1},emit:function(e){var t=this._listeners&&this._listeners[e],n=Array.prototype.slice.call(arguments,1);e=e.charAt(0).toUpperCase()+e.slice(1),this["on"+e]&&this["on"+e].apply(this,n),t&&t.forEach(function(e){e.callback.apply(e.context||null,n)})},on:function(e,t,n){return this.addListener(e,t,n)},off:function(e,t,n){return this.removeListener(e,t,n)}},a=e({mixins:[s],statics:{PlatformType:{DEFAULT:"Default",AIRPLAY:"AirPlay",GOOGLE_CAST:"GoogleCast",WEBOS_NATIVE:"WebOSNative",WEBOS_WEB_APP:"WebOSWebApp"},EventType:{MESSAGE:"message",PAUSE:"pause",PLAY:"play",READY:"ready",STOP:"stop",STATUS:"mediaStatusUpdate",JOIN:"join",DEPART:"depart"}},mediaEvents:{loadstart:"buffering",playing:"playing",waiting:"buffering",abort:"finished",ended:"finished",play:"playing",pause:"paused"},constructor:function(){this.handleMediaEvent=this.handleMediaEvent.bind(this),t(this,i[this._detectPlatform()])},setImageElement:function(e){e&&(this.imageElement=e)},setMediaElement:function(e){this.mediaElement&&this.unregisterMediaEvents(this.mediaElement),e&&(this.registerMediaEvents(e),this.mediaElement=e,this.mediaElement.autoPlay=!0,this.setMediaStatus("idle"))},setMediaStatus:function(e){this.mediaStatus=e,this.emit(a.EventType.STATUS,e)},registerMediaEvents:function(e){if(e)for(var t in this.mediaEvents)this.mediaEvents.hasOwnProperty(t)&&e.addEventListener(t,this.handleMediaEvent,!1)},unregisterMediaEvents:function(e){if(e)for(var t in this.mediaEvents)this.mediaEvents.hasOwnProperty(t)&&e.removeEventListener(t,this.handleMediaEvent,!1)},handleMediaEvent:function(e){this.mediaEvents.hasOwnProperty(e.type)&&this.setMediaStatus(this.mediaEvents[e.type])},handleReady:function(){this.emit(a.EventType.READY)},handleJoin:function(e){this.emit(a.EventType.JOIN,e)},handleDepart:function(e){this.emit(a.EventType.DEPART,e)},_detectPlatform:function(){var e=navigator.userAgent.toLowerCase();return this.platformType=a.PlatformType.DEFAULT,/(iPad|iPhone|iPod)/g.test(navigator.userAgent)?this.platformType=a.PlatformType.AIRPLAY:e.indexOf("crkey")>0&&null!=cast?this.platformType=a.PlatformType.GOOGLE_CAST:(e.indexOf("tv")>=0&&e.indexOf("webos")>=0||e.indexOf("web0s")>=0)&&(this.platformType=window.PalmServiceBridge?a.PlatformType.WEBOS_NATIVE:a.PlatformType.WEBOS_WEB_APP),this.platformType},init:n,sendMessage:n,broadcastMessage:n}),i={};i.Default={interactive:!1,init:n,sendMessage:n,broadcastMessage:n};var o={onLoadImage:function(e){var t=this.imageElement;t&&e&&e.mediaURL?(console.log("Loading image",e.mediaURL),t.src=e.mediaURL):console.log("Failed to load image")},onLoadMedia:function(e){var t=this.mediaElement;t&&e&&e.mediaURL?(console.log("Loading",e.mediaURL),t.src=e.mediaURL,t.load()):console.log("Failed to load media")},handleDisplayImage:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId;this.emit("loadImage",n),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleGetDuration:function(e){var t=e.from,n=e.message.mediaCommand.type,s=e.message.mediaCommand.requestId,a=this.mediaElement,i=a&&a.duration||0;this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:n,duration:i,requestId:s}})},handleGetPosition:function(e){var t=e.from,n=e.message.mediaCommand.type,s=e.message.mediaCommand.requestId,a=this.mediaElement,i=a&&a.currentTime||0;this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:n,position:i,requestId:s}})},handleMediaStatusUpdate:function(e){var t=this.mediaStatus,n=0,s=0,a=this.mediaElement;if(a){if(n=a.currentTime,0/0!=a.duration&&(s=a.duration),null==t)return;this.broadcastMessage({contentType:"connectsdk.mediaEvent",mediaEvent:{type:"playState",playState:t,position:n,duration:s,requestId:e?e:-1}})}},handlePlayMedia:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId;this.emit("loadMedia",n),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleSeek:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=n.position;if(i){var o=this.mediaElement;o&&(o.currentTime=i),this.handleMediaStatusUpdate(-1)}this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handlePlay:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=this.mediaElement;i&&i.play(),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handlePause:function(e){var t=e.from,n=e.message.mediaCommand,s=n.type,a=n.requestId,i=this.mediaElement;i&&i.pause(),this.sendMessage(t,{contentType:"connectsdk.mediaCommandResponse",mediaCommandResponse:{type:s,requestId:a}})},handleMessage:function(e){var t=null;if(null!=e&&null!=e.message&&(t=e.message.contentType,null==t))try{t=JSON.parse(e.message).contentType}catch(n){}switch(t){case"connectsdk.mediaCommand":this.handleMediaCommand(e);break;case"connectsdk.serviceCommand":this.handleServiceCommand(e);break;default:this.emit(a.EventType.MESSAGE,e)}},handleMediaCommand:function(e){var t=e.message.mediaCommand;if(t){var n=t.type;switch(console.log("processing mediaCommand "+JSON.stringify(t)+" of type "+n),n){case"displayImage":this.handleDisplayImage(e);break;case"getDuration":this.handleGetDuration(e);break;case"getPosition":this.handleGetPosition(e);break;case"playMedia":this.handlePlayMedia(e);break;case"seek":this.handleSeek(e);break;case"play":this.handlePlay(e);break;case"pause":this.handlePause(e)}}},handleServiceCommand:function(e){var t=e.message.serviceCommand;if(t){var n=t.type;switch(console.log("processing serviceCommand "+JSON.stringify(t)+" of type "+n),n){case"close":var s=window.open(window.location,"_self");null!=s?s.close():window.close()}}}},r=t({interactive:!0,init:function(){window.addEventListener("keydown",this.handleKeyDown.bind(this)),this.on(a.EventType.STATUS,this.handleMediaStatusUpdate.bind(this)),this.webOSAppChannels=new c,this.webOSAppChannels.on("message",this.handleMessage.bind(this)),this.webOSAppChannels.on("ready",this.handleReady.bind(this)),this.webOSAppChannels.on("join",this.handleJoin.bind(this)),this.webOSAppChannels.on("depart",this.handleDepart.bind(this)),this.webOSAppChannels.start()},sendMessage:function(e,t){this.webOSAppChannels.sendMessage(e,t)},broadcastMessage:function(e){this.webOSAppChannels.broadcastMessage(e)},handleKeyDown:function(e){if(this.mediaElement)switch(e.keyCode){case 415:console.log(this.name+" :: play command received"),this.mediaElement.play(),this.emit(a.EventType.PLAY);break;case 19:console.log(this.name+" :: pause command received"),this.mediaElement.pause(),this.emit(a.EventType.PAUSE);break;case 413:console.log(this.name+" :: stop command received"),this.emit(a.EventType.STOP)}}},o);i.WebOSNative=t({name:"webOS Native Web App"},r),i.WebOSWebApp=t({name:"webOS Web App"},r),i.AirPlay=t({name:"AirPlay",interactive:!0,init:function(){this.on(a.EventType.STATUS,this.handleMediaStatusUpdate.bind(this))},sendMessage:function(e,t){this.broadcastMessage(t)},broadcastMessage:function(e){var t;t="string"==typeof e?e:JSON.stringify(e);var n=document.createElement("IFRAME");n.setAttribute("src","connectsdk://"+t),document.documentElement.appendChild(n),n.parentNode.removeChild(n),n=null}},o),i.GoogleCast={name:"Google Cast",interactive:!1,init:function(){var e=this.setMediaElement;this.setMediaElement=function(t){e.apply(this,arguments),this._setCastElement(t)},this._setCastElement(this.mediaElement),window.castReceiverManager=cast.receiver.CastReceiverManager.getInstance(),window.castReceiverManager.addEventListener("ready",this._handleReady.bind(this)),window.castMessageBus=window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk"),window.castMessageBus.addEventListener("message",this.handleMessage.bind(this)),window.castReceiverManager.start()},_handleReady:function(e){window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_CONNECTED,this.handleSenderConnected.bind(this)),window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_DISCONNECTED,this.handleSenderDisconnected.bind(this)),this.handleReady(e)},_setCastElement:function(e){e&&(window.castMediaManager?window.castMediaManager.setMediaElement(e):window.castMediaManager=new cast.receiver.MediaManager(e))},handleMessage:function(e){var t;try{t=JSON.parse(e.data)}catch(n){t=e.data}this.emit(a.EventType.MESSAGE,{from:e.senderId,message:t})},sendMessage:function(e,t){var n;if("string"==typeof t)window.castMessageBus.send(e,t);else{var n=JSON.stringify(t);n&&window.castMessageBus.send(e,n)}},broadcastMessage:function(e){var t;if("string"==typeof e)window.castMessageBus.broadcast(e);else{var t=JSON.stringify(e);t&&window.castMessageBus.broadcast(t)}},handleSenderConnected:function(e){null!=e&&null!=e.senderId&&(e.id=e.senderId,this.emit(a.EventType.JOIN,e))},handleSenderDisconnected:function(e){null!=e&&null!=e.senderId&&(e.id=e.senderId,this.emit(a.EventType.DEPART,e))}};var d,c=e({mixins:[s],constructor:function(){function e(e,t,n){var s=new window.PalmServiceBridge;return s.onservicecallback=function(e){n(JSON.parse(e))},s.call(e,JSON.stringify(t||{})),s}function t(e){for(var t,n=e.split("&"),s={},a=0,i=n.length;i>a;a++)t=n[a].split("="),s[t[0]]=decodeURIComponent(t[1]);return s}if(d)return d;if(d=this,this.stopRequested=!1,this.ws=null,this.channels=[],window.PalmServiceBridge){var n,s="com.webos.service.secondscreen.gateway",a="luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel",i="luna://com.palm.bus/signal/registerServerStatus";this.getAppChannelWebSocket=function(t){statusSubcription=e(i,{subscribe:!0,serviceName:s},function(s){s&&s.connected&&(n=e(a,{},function(e){e.socketUrl&&t(new WebSocket(e.socketUrl))}))})}}else{var o=!1;if(window.location.search){console.log("found params: ",window.location.search);var r=t(window.location.search.substr(1));r.webOSAppChannelSocketUrl&&(console.log("found websocket URL: ",r.webOSAppChannelSocketUrl),o=!0,this.getAppChannelWebSocket=function(e){e(new WebSocket(r.webOSAppChannelSocketUrl))})}if(!o&&window.NetCastCreateAppChannel){var c="_webOSCreateAppChannelCallback";this.getAppChannelWebSocket=function(e){window[c]=function(t){delete window[c],t.socketUrl&&e(new WebSocket(t.socketUrl))},window.NetCastCreateAppChannel("{}",c,!1)}}}},getAppChannelWebSocket:function(){console.error("app channel socket not supported")},start:function(){this.stopRequested=!1;var e=this;!this.ws&&this.getAppChannelWebSocket(function(t){return e.stopRequested?(e.stop(),void 0):(e.ws=t,e.ws.onopen=function(t){console.log("websocket opened"),e.emit("ready",t)},e.ws.onerror=function(e){console.log("websocket error:",e)},e.ws.onmessage=function(t){try{var n=JSON.parse(t.data)}catch(s){return}switch(console.log("got message: "+JSON.stringify(n)),n.type){case"p2p":e._handleP2PMessage(n);break;case"p2p.depart":e._handleP2PDepart(n);break;case"p2p.join":e._handleP2PJoin(n);break;case"p2p.join-request":e._handleP2PJoinRequest(n)}},e.ws.onclose=function(){e.ws=null},void 0)})},stop:function(){this.stopRequested=!0,this.ws&&this.ws.close(),this._destroy()},sendMessage:function(e,t){var n={type:"p2p",to:e,payload:t};this._send(n)},broadcastMessage:function(e){var t={type:"p2p",payload:e};this._send(t)},_send:function(e){this.ws&&e&&(console.log("sending message: ",e),this.ws.send(JSON.stringify(e)))},_destroy:function(){this.ws&&(this.ws=null)},_handleP2PMessage:function(e){var t=e.payload;t&&(console.log("processing message payload "+JSON.stringify(t)),this.emit("message",{from:e.from,message:e.payload}))},_handleP2PJoinRequest:function(e){var t=e.payload;t&&this._send({type:"p2p.join-response",payload:{allowJoin:!0,requestId:t.requestId}})},_handleP2PJoin:function(e){var t=e.client;t&&(console.log("processing client join "+JSON.stringify(t)),this.emit(a.EventType.JOIN,t))},_handleP2PDepart:function(e){var t=e.from;t&&(console.log("processing client departure "+t),this.emit(a.EventType.DEPART,{id:t}))}});return{ConnectManager:a,WebOSAppChannels:c}}(); -------------------------------------------------------------------------------- /src/connect_bridge.js: -------------------------------------------------------------------------------- 1 | var connectsdk = (function () { 2 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3 | // Mixins 4 | 5 | // Event emitter 6 | var SimpleEventEmitter = { 7 | addListener: function (event, callback, context) { 8 | if (!event) { throw new Error("missing parameter: event"); } 9 | if (!callback) { throw new Error("missing parameter: callback"); } 10 | 11 | this._listeners = this._listeners || {}; 12 | if (!this._listeners) this._listeners = {}; 13 | if (!this._listeners[event]) this._listeners[event] = []; 14 | this._listeners[event].push({callback: callback, context: context}); 15 | 16 | this.emit("_addListener", event); 17 | 18 | return this; 19 | }, 20 | 21 | removeListener: function (event, callback, context) { 22 | if (this._listeners && this._listeners[event]) { 23 | this._listeners[event] = this._listeners[event].filter(function (l) { 24 | return (callback && callback !== l.callback) && (context && context !== l.context); 25 | }); 26 | } 27 | 28 | this.emit("_removeListener", event); 29 | 30 | return this; 31 | }, 32 | 33 | hasListeners: function (event) { 34 | if (event) { 35 | return (this._listeners && this._listeners[event] && this._listeners[event].length > 0); 36 | } else { 37 | for (event in this._listeners) { 38 | if (event[0] !== "_" && this._listeners.hasOwnProperty(event) && this._listeners[event].length > 0) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | }, 45 | 46 | emit: function (event) { 47 | var listeners = this._listeners && this._listeners[event]; 48 | var args = Array.prototype.slice.call(arguments, 1); 49 | 50 | // upper-case first char 51 | event = event.charAt(0).toUpperCase() + event.slice(1); 52 | 53 | if (this["on" + event]) { 54 | this["on" + event].apply(this, args); 55 | } 56 | 57 | if (listeners) { 58 | listeners.forEach(function (l) { 59 | l.callback.apply(l.context || null, args); 60 | }); 61 | } 62 | }, 63 | 64 | on: function (event, callback, context) { 65 | return this.addListener(event, callback, context); 66 | }, 67 | 68 | off: function (event, callback, context) { 69 | return this.removeListener(event, callback, context); 70 | } 71 | }; 72 | 73 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 74 | // ConnectManager 75 | 76 | var ConnectManager = createClass({ 77 | mixins: [SimpleEventEmitter], 78 | 79 | statics: { 80 | PlatformType: { 81 | DEFAULT: "Default", 82 | AIRPLAY: "AirPlay", 83 | GOOGLE_CAST: "GoogleCast", 84 | WEBOS_NATIVE: "WebOSNative", 85 | WEBOS_WEB_APP: "WebOSWebApp" 86 | }, 87 | 88 | EventType: { 89 | MESSAGE: "message", 90 | PAUSE: "pause", 91 | PLAY: "play", 92 | READY: "ready", 93 | STOP: "stop", 94 | STATUS: "mediaStatusUpdate", 95 | JOIN: "join", 96 | DEPART: "depart" 97 | } 98 | }, 99 | 100 | mediaEvents: { 101 | loadstart: "buffering", 102 | playing: "playing", 103 | waiting: "buffering", 104 | abort: "finished", 105 | ended: "finished", 106 | play: "playing", 107 | pause: "paused" 108 | }, 109 | 110 | constructor: function () { 111 | this.handleMediaEvent = this.handleMediaEvent.bind(this); 112 | 113 | extend(this, platforms[this._detectPlatform()]); 114 | }, 115 | 116 | setImageElement: function (element) { 117 | // Register new image element 118 | if (element) { 119 | this.imageElement = element; 120 | } 121 | }, 122 | 123 | setMediaElement: function (element) { 124 | // Unregister existing media element 125 | this.mediaElement && this.unregisterMediaEvents(this.mediaElement); 126 | 127 | // Register new media element 128 | if (element) { 129 | this.registerMediaEvents(element); 130 | this.mediaElement = element; 131 | this.mediaElement.autoPlay = true; 132 | this.setMediaStatus("idle"); 133 | } 134 | }, 135 | 136 | setMediaStatus: function (status) { 137 | this.mediaStatus = status; 138 | this.emit(ConnectManager.EventType.STATUS, status); 139 | }, 140 | 141 | registerMediaEvents: function (element) { 142 | if (element) { 143 | for (var key in this.mediaEvents) { 144 | this.mediaEvents.hasOwnProperty(key) && element.addEventListener(key, this.handleMediaEvent, false); 145 | } 146 | } 147 | }, 148 | 149 | unregisterMediaEvents: function (element) { 150 | if (element) { 151 | for (var key in this.mediaEvents) { 152 | this.mediaEvents.hasOwnProperty(key) && element.removeEventListener(key, this.handleMediaEvent, false); 153 | } 154 | } 155 | }, 156 | 157 | handleMediaEvent: function (evt) { 158 | this.mediaEvents.hasOwnProperty(evt.type) && this.setMediaStatus(this.mediaEvents[evt.type]); 159 | }, 160 | 161 | handleReady: function (evt) { 162 | this.emit(ConnectManager.EventType.READY); 163 | }, 164 | 165 | handleJoin: function (client) { 166 | this.emit(ConnectManager.EventType.JOIN, client); 167 | }, 168 | 169 | handleDepart: function (client) { 170 | this.emit(ConnectManager.EventType.DEPART, client); 171 | }, 172 | 173 | _detectPlatform: function() { 174 | var userAgent = navigator.userAgent.toLowerCase(); 175 | this.platformType = ConnectManager.PlatformType.DEFAULT; 176 | 177 | if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) 178 | this.platformType = ConnectManager.PlatformType.AIRPLAY; 179 | else if (userAgent.indexOf('crkey') > 0 && cast != null) 180 | this.platformType = ConnectManager.PlatformType.GOOGLE_CAST; 181 | else if (userAgent.indexOf('tv') >= 0 && (userAgent.indexOf('webos') >= 0) || (userAgent.indexOf('web0s') >= 0)) 182 | { 183 | if (window.PalmServiceBridge) 184 | this.platformType = ConnectManager.PlatformType.WEBOS_NATIVE; 185 | else 186 | this.platformType = ConnectManager.PlatformType.WEBOS_WEB_APP; 187 | } 188 | return this.platformType; 189 | }, 190 | 191 | init: nop, 192 | 193 | sendMessage: nop, 194 | 195 | broadcastMessage: nop 196 | }); 197 | 198 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 199 | // Platforms 200 | 201 | var platforms = {}; 202 | 203 | // Default 204 | platforms.Default = { 205 | interactive: false, 206 | init: nop, 207 | sendMessage: nop, 208 | broadcastMessage: nop 209 | }; 210 | 211 | // Base media player (JSON media playback & control commands) 212 | 213 | var BaseMediaPlayer = { 214 | 215 | onLoadImage: function (image) { 216 | var imageElement = this.imageElement; 217 | if (imageElement && image && image.mediaURL) { 218 | console.log("Loading image", image.mediaURL); 219 | imageElement.src = image.mediaURL; 220 | } else { 221 | console.log("Failed to load image"); 222 | } 223 | }, 224 | 225 | onLoadMedia: function (media) { 226 | var mediaElement = this.mediaElement; 227 | if (mediaElement && media && media.mediaURL) { 228 | console.log("Loading", media.mediaURL); 229 | // TODO: pull metadata 230 | mediaElement.src = media.mediaURL; 231 | mediaElement.load(); 232 | } else { 233 | console.log("Failed to load media"); 234 | } 235 | }, 236 | 237 | handleDisplayImage: function (msgData) { 238 | var from = msgData.from; 239 | var mediaCommand = msgData.message.mediaCommand; 240 | var commandType = mediaCommand.type; 241 | var requestId = mediaCommand.requestId; 242 | 243 | this.emit('loadImage', mediaCommand); 244 | 245 | this.sendMessage(from, { 246 | contentType: 'connectsdk.mediaCommandResponse', 247 | mediaCommandResponse: { 248 | type: commandType, 249 | requestId: requestId 250 | } 251 | }); 252 | }, 253 | 254 | handleGetDuration: function (msgData) { 255 | var from = msgData.from; 256 | var commandType = msgData.message.mediaCommand.type; 257 | var requestId = msgData.message.mediaCommand.requestId; 258 | var mediaElement = this.mediaElement; 259 | var duration = (mediaElement && mediaElement.duration) || 0; 260 | 261 | this.sendMessage(from, { 262 | contentType: 'connectsdk.mediaCommandResponse', 263 | mediaCommandResponse: { 264 | type: commandType, 265 | duration: duration, 266 | requestId: requestId 267 | } 268 | }); 269 | }, 270 | 271 | handleGetPosition: function (msgData) { 272 | var from = msgData.from; 273 | var commandType = msgData.message.mediaCommand.type; 274 | var requestId = msgData.message.mediaCommand.requestId; 275 | var mediaElement = this.mediaElement; 276 | var currentTime = (mediaElement && mediaElement.currentTime) || 0; 277 | 278 | this.sendMessage(from, { 279 | contentType: 'connectsdk.mediaCommandResponse', 280 | mediaCommandResponse: { 281 | type: commandType, 282 | position: currentTime, 283 | requestId: requestId 284 | } 285 | }); 286 | }, 287 | 288 | handleMediaStatusUpdate: function (requestId) { 289 | var playState = this.mediaStatus; 290 | var currentTime = 0; 291 | var duration = 0; 292 | var mediaElement = this.mediaElement; 293 | 294 | if (mediaElement) { 295 | currentTime = mediaElement.currentTime; 296 | 297 | if (mediaElement.duration != NaN) 298 | duration = mediaElement.duration; 299 | 300 | if (playState == null) 301 | return; 302 | 303 | this.broadcastMessage({ 304 | contentType: 'connectsdk.mediaEvent', 305 | mediaEvent: { 306 | type: 'playState', 307 | playState: playState, 308 | position: currentTime, 309 | duration: duration, 310 | requestId: requestId ? requestId : -1 311 | } 312 | }); 313 | } 314 | }, 315 | 316 | handlePlayMedia: function (msgData) { 317 | var from = msgData.from; 318 | var mediaCommand = msgData.message.mediaCommand; 319 | var commandType = mediaCommand.type; 320 | var requestId = mediaCommand.requestId; 321 | 322 | this.emit('loadMedia', mediaCommand); 323 | 324 | this.sendMessage(from, { 325 | contentType: 'connectsdk.mediaCommandResponse', 326 | mediaCommandResponse: { 327 | type: commandType, 328 | requestId: requestId 329 | } 330 | }); 331 | }, 332 | 333 | handleSeek: function (msgData) { 334 | var from = msgData.from; 335 | var mediaCommand = msgData.message.mediaCommand; 336 | var commandType = mediaCommand.type; 337 | var requestId = mediaCommand.requestId; 338 | var position = mediaCommand.position; 339 | 340 | if (position) { 341 | var mediaElement = this.mediaElement; 342 | mediaElement && (mediaElement.currentTime = position); 343 | this.handleMediaStatusUpdate(-1); 344 | } 345 | 346 | this.sendMessage(from, { 347 | contentType: 'connectsdk.mediaCommandResponse', 348 | mediaCommandResponse: { 349 | type: commandType, 350 | requestId: requestId 351 | } 352 | }); 353 | }, 354 | 355 | handlePlay: function (msgData) { 356 | var from = msgData.from; 357 | var mediaCommand = msgData.message.mediaCommand; 358 | var commandType = mediaCommand.type; 359 | var requestId = mediaCommand.requestId; 360 | 361 | var mediaElement = this.mediaElement; 362 | mediaElement && mediaElement.play(); 363 | 364 | this.sendMessage(from, { 365 | contentType: 'connectsdk.mediaCommandResponse', 366 | mediaCommandResponse: { 367 | type: commandType, 368 | requestId: requestId 369 | } 370 | }); 371 | }, 372 | 373 | handlePause: function (msgData) { 374 | var from = msgData.from; 375 | var mediaCommand = msgData.message.mediaCommand; 376 | var commandType = mediaCommand.type; 377 | var requestId = mediaCommand.requestId; 378 | 379 | var mediaElement = this.mediaElement; 380 | mediaElement && mediaElement.pause(); 381 | 382 | this.sendMessage(from, { 383 | contentType: 'connectsdk.mediaCommandResponse', 384 | mediaCommandResponse: { 385 | type: commandType, 386 | requestId: requestId 387 | } 388 | }); 389 | }, 390 | 391 | handleMessage: function (msgData) { 392 | var contentType = null; 393 | 394 | if (msgData != null && msgData.message != null) { 395 | contentType = msgData.message.contentType; 396 | 397 | if (contentType == null) { 398 | try { 399 | contentType = JSON.parse(msgData.message).contentType; 400 | } catch (ex) { 401 | // don't need to do anything here 402 | } 403 | } 404 | } 405 | 406 | switch (contentType) { 407 | case "connectsdk.mediaCommand": 408 | this.handleMediaCommand(msgData); 409 | break; 410 | 411 | case "connectsdk.serviceCommand": 412 | this.handleServiceCommand(msgData); 413 | break; 414 | 415 | default: 416 | this.emit(ConnectManager.EventType.MESSAGE, msgData); 417 | } 418 | }, 419 | 420 | handleMediaCommand: function (msgData) { 421 | var mediaCommand = msgData.message.mediaCommand; 422 | if (!mediaCommand) { 423 | return; 424 | } 425 | 426 | var commandType = mediaCommand.type; 427 | console.log('processing mediaCommand ' + JSON.stringify(mediaCommand) + ' of type ' + commandType); 428 | 429 | switch (commandType) { 430 | case "displayImage": 431 | this.handleDisplayImage(msgData); 432 | break; 433 | case "getDuration": 434 | this.handleGetDuration(msgData); 435 | break; 436 | case "getPosition": 437 | this.handleGetPosition(msgData); 438 | break; 439 | case "playMedia": 440 | this.handlePlayMedia(msgData); 441 | break; 442 | case "seek": 443 | this.handleSeek(msgData); 444 | break; 445 | case "play": 446 | this.handlePlay(msgData); 447 | break; 448 | case "pause": 449 | this.handlePause(msgData); 450 | break; 451 | } 452 | }, 453 | 454 | handleServiceCommand: function (msgData) { 455 | var serviceCommand = msgData.message.serviceCommand; 456 | if (!serviceCommand) { 457 | return; 458 | } 459 | 460 | var commandType = serviceCommand.type; 461 | console.log('processing serviceCommand ' + JSON.stringify(serviceCommand) + ' of type ' + commandType); 462 | 463 | switch (commandType) { 464 | case "close": 465 | // this is a hack to circumvent the fact that window.close() doesn't work with the webOS app type 466 | var newWindow = window.open(window.location, '_self'); 467 | 468 | if (newWindow != null) 469 | newWindow.close(); 470 | else 471 | window.close(); 472 | break; 473 | } 474 | } 475 | }; 476 | 477 | // webOS 478 | var WebOSCommon = extend({ 479 | interactive: true, 480 | init: function () { 481 | window.addEventListener("keydown", this.handleKeyDown.bind(this)); 482 | this.on(ConnectManager.EventType.STATUS, this.handleMediaStatusUpdate.bind(this)); 483 | 484 | this.webOSAppChannels = new WebOSAppChannels(); 485 | this.webOSAppChannels.on('message', this.handleMessage.bind(this)); 486 | this.webOSAppChannels.on('ready', this.handleReady.bind(this)); 487 | this.webOSAppChannels.on('join', this.handleJoin.bind(this)); 488 | this.webOSAppChannels.on('depart', this.handleDepart.bind(this)); 489 | this.webOSAppChannels.start(); 490 | }, 491 | 492 | sendMessage: function (to, message) { 493 | this.webOSAppChannels.sendMessage(to, message); 494 | }, 495 | 496 | broadcastMessage: function (message) { 497 | this.webOSAppChannels.broadcastMessage(message); 498 | }, 499 | 500 | handleKeyDown: function (evt) { 501 | if (!this.mediaElement) { 502 | return; 503 | } 504 | 505 | switch (evt.keyCode) { 506 | case 415: // PLAY 507 | console.log(this.name + " :: play command received"); 508 | this.mediaElement.play(); 509 | this.emit(ConnectManager.EventType.PLAY); 510 | break; 511 | 512 | case 19: // PAUSE 513 | console.log(this.name + " :: pause command received"); 514 | this.mediaElement.pause(); 515 | this.emit(ConnectManager.EventType.PAUSE); 516 | break; 517 | 518 | case 413: // STOP 519 | console.log(this.name + " :: stop command received"); 520 | this.emit(ConnectManager.EventType.STOP); 521 | break; 522 | } 523 | } 524 | }, BaseMediaPlayer); 525 | 526 | platforms.WebOSNative = extend({ 527 | name: "webOS Native Web App" 528 | }, WebOSCommon); 529 | 530 | platforms.WebOSWebApp = extend({ 531 | name: "webOS Web App" 532 | }, WebOSCommon); 533 | 534 | // AirPlay 535 | platforms.AirPlay = extend({ 536 | name: "AirPlay", 537 | interactive: true, 538 | 539 | init: function() { 540 | this.on(ConnectManager.EventType.STATUS, this.handleMediaStatusUpdate.bind(this)); 541 | }, 542 | 543 | sendMessage: function (to, message) { 544 | // AirPlay does not have p2p support, so we'll just 'broadcast' this message 545 | this.broadcastMessage(message); 546 | }, 547 | 548 | broadcastMessage: function (message) { 549 | var messageString; 550 | 551 | if (typeof message == 'string') 552 | messageString = message; 553 | else 554 | messageString = JSON.stringify(message); 555 | 556 | var iframe = document.createElement('IFRAME'); 557 | iframe.setAttribute('src', 'connectsdk://' + messageString); 558 | document.documentElement.appendChild(iframe); 559 | iframe.parentNode.removeChild(iframe); 560 | iframe = null; 561 | } 562 | }, BaseMediaPlayer), 563 | 564 | // Google Cast 565 | platforms.GoogleCast = { 566 | name: "Google Cast", 567 | interactive: false, 568 | init: function () { 569 | var origSetMediaElement = this.setMediaElement; 570 | this.setMediaElement = function (element) { 571 | origSetMediaElement.apply(this, arguments); 572 | this._setCastElement(element); 573 | }; 574 | 575 | this._setCastElement(this.mediaElement); 576 | window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); 577 | window.castReceiverManager.addEventListener("ready", this._handleReady.bind(this)); 578 | 579 | window.castMessageBus = window.castReceiverManager.getCastMessageBus("urn:x-cast:com.connectsdk"); 580 | window.castMessageBus.addEventListener("message", this.handleMessage.bind(this)); 581 | 582 | window.castReceiverManager.start(); 583 | }, 584 | 585 | _handleReady: function(evt) { 586 | window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_CONNECTED, this.handleSenderConnected.bind(this)); 587 | window.castReceiverManager.addEventListener(cast.receiver.CastReceiverManager.EventType.SENDER_DISCONNECTED, this.handleSenderDisconnected.bind(this)); 588 | 589 | this.handleReady(evt); 590 | }, 591 | 592 | _setCastElement: function (element) { 593 | if (!element) { 594 | return; 595 | } 596 | if (!window.castMediaManager) { 597 | window.castMediaManager = new cast.receiver.MediaManager(element); 598 | } else { 599 | window.castMediaManager.setMediaElement(element); 600 | } 601 | }, 602 | 603 | handleMessage: function (evt) { 604 | var message; 605 | try { 606 | message = JSON.parse(evt.data); 607 | } catch (ex) { 608 | message = evt.data; 609 | } 610 | 611 | this.emit(ConnectManager.EventType.MESSAGE, { from: evt.senderId, message: message }); 612 | }, 613 | 614 | sendMessage: function (to, message) { 615 | var messageString; 616 | 617 | if (typeof message == 'string') 618 | window.castMessageBus.send(to, message); 619 | else 620 | { 621 | var messageString = JSON.stringify(message); 622 | 623 | if (messageString) 624 | window.castMessageBus.send(to, messageString); 625 | } 626 | }, 627 | 628 | broadcastMessage: function (message) { 629 | var messageString; 630 | 631 | if (typeof message == 'string') 632 | window.castMessageBus.broadcast(message); 633 | else 634 | { 635 | var messageString = JSON.stringify(message); 636 | 637 | if (messageString) 638 | window.castMessageBus.broadcast(messageString); 639 | } 640 | }, 641 | 642 | handleSenderConnected: function(sender) { 643 | if (sender == null || sender.senderId == null) 644 | return; 645 | 646 | sender.id = sender.senderId; 647 | 648 | this.emit(ConnectManager.EventType.JOIN, sender); 649 | }, 650 | 651 | handleSenderDisconnected: function(sender) { 652 | if (sender == null || sender.senderId == null) 653 | return; 654 | 655 | sender.id = sender.senderId; 656 | 657 | this.emit(ConnectManager.EventType.DEPART, sender); 658 | } 659 | }; 660 | 661 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 662 | // WebOSAppChannels 663 | 664 | var WebOSAppChannelsInstance; 665 | var WebOSAppChannels = createClass({ 666 | mixins: [SimpleEventEmitter], 667 | 668 | constructor: function() { 669 | // Singleton logic 670 | if (WebOSAppChannelsInstance) { 671 | return WebOSAppChannelsInstance; 672 | } 673 | WebOSAppChannelsInstance = this; 674 | 675 | this.stopRequested = false; 676 | this.ws = null; 677 | this.channels = []; 678 | 679 | if (window.PalmServiceBridge) { 680 | var statusSubscription; 681 | var getChannelRequest; 682 | 683 | var GATEWAY_SERVICE = "com.webos.service.secondscreen.gateway"; 684 | var CREATE_APPCHANNEL_URI = "luna://com.webos.service.secondscreen.gateway/app2app/createAppChannel"; 685 | var REGISTER_SERVER_STATUS_URI = "luna://com.palm.bus/signal/registerServerStatus"; 686 | 687 | function callService(uri, params, callback) { 688 | var request = new window.PalmServiceBridge(); 689 | 690 | request.onservicecallback = function (responseString) { 691 | callback(JSON.parse(responseString)); 692 | }; 693 | 694 | request.call(uri, JSON.stringify(params || {})); 695 | 696 | return request; 697 | } 698 | 699 | this.getAppChannelWebSocket = function (callback) { 700 | statusSubcription = callService(REGISTER_SERVER_STATUS_URI, { 701 | "subscribe": true, serviceName: GATEWAY_SERVICE 702 | }, function (response) { 703 | if (response && response.connected) { 704 | getChannelRequest = callService(CREATE_APPCHANNEL_URI, {}, function (response) { 705 | if (response.socketUrl) { 706 | callback(new WebSocket(response.socketUrl)); 707 | } 708 | }); 709 | } 710 | }); 711 | }; 712 | } else { 713 | // Look for "webOSAppChannelSocketUrl" parameter 714 | var foundSocketUrl = false; 715 | if (window.location.search) { 716 | console.log("found params: ", window.location.search); 717 | /** 718 | * @author Konstantin Kitmanov 719 | * May be freely distributed under the MIT license. 720 | */ 721 | function parse(str) { 722 | var chunks = str.split('&'), 723 | dict = {}, 724 | chunk; 725 | for (var i = 0, len = chunks.length; i < len; i++) { 726 | chunk = chunks[i].split('='); 727 | dict[chunk[0]] = decodeURIComponent(chunk[1]); 728 | } 729 | 730 | return dict; 731 | } 732 | 733 | var parsed = parse(window.location.search.substr(1)); 734 | 735 | if (parsed.webOSAppChannelSocketUrl) { 736 | console.log("found websocket URL: ", parsed.webOSAppChannelSocketUrl); 737 | foundSocketUrl = true; 738 | 739 | this.getAppChannelWebSocket = function (callback) { 740 | callback(new WebSocket(parsed.webOSAppChannelSocketUrl)); 741 | }; 742 | } 743 | } 744 | 745 | if (!foundSocketUrl && window.NetCastCreateAppChannel) { 746 | var callbackName = "_webOSCreateAppChannelCallback"; 747 | 748 | this.getAppChannelWebSocket = function (callback) { 749 | 750 | window[callbackName] = function (response) { 751 | delete window[callbackName]; 752 | //console.log("got NetCastCreateAppChannel response: " + JSON.stringify(response)); 753 | 754 | if (response.socketUrl) { 755 | callback(new WebSocket(response.socketUrl)); 756 | } 757 | }; 758 | 759 | window.NetCastCreateAppChannel('{}', callbackName, false); 760 | }; 761 | } 762 | } 763 | }, 764 | 765 | getAppChannelWebSocket: function (callback) { 766 | console.error("app channel socket not supported"); 767 | }, 768 | 769 | start: function () { 770 | this.stopRequested = false; 771 | var self = this; 772 | 773 | !this.ws && this.getAppChannelWebSocket(function (socket) { 774 | if (self.stopRequested) { 775 | self.stop(); 776 | return; 777 | } 778 | 779 | self.ws = socket; 780 | 781 | self.ws.onopen = function (event) { 782 | console.log("websocket opened"); 783 | self.emit('ready', event); 784 | }; 785 | 786 | self.ws.onerror = function (error) { 787 | console.log("websocket error:", error); 788 | }; 789 | 790 | self.ws.onmessage = function (event) { 791 | try { 792 | var message = JSON.parse(event.data); 793 | } catch (e) { 794 | return; // Ignore the message if it doesn't parse. 795 | } 796 | console.log("got message: " + JSON.stringify(message)); 797 | 798 | switch (message.type) { 799 | case "p2p": 800 | self._handleP2PMessage(message); 801 | break; 802 | case "p2p.depart": 803 | self._handleP2PDepart(message); 804 | break; 805 | case "p2p.join": 806 | self._handleP2PJoin(message); 807 | break; 808 | case "p2p.join-request": 809 | self._handleP2PJoinRequest(message); 810 | break; 811 | } 812 | }; 813 | 814 | self.ws.onclose = function () { 815 | self.ws = null; 816 | }; 817 | }); 818 | }, 819 | 820 | stop: function () { 821 | this.stopRequested = true; 822 | 823 | if (this.ws) { 824 | this.ws.close(); 825 | } 826 | 827 | this._destroy(); 828 | }, 829 | 830 | sendMessage: function(to, message) { 831 | var messageData = { 832 | type: "p2p", 833 | to: to, // TODO: do we need to sanitize/check this value? 834 | payload: message 835 | }; 836 | 837 | this._send(messageData); 838 | }, 839 | 840 | broadcastMessage: function(message) { 841 | var messageData = { 842 | type: "p2p", 843 | payload: message 844 | }; 845 | 846 | this._send(messageData); 847 | }, 848 | 849 | _send: function (message) { 850 | if (this.ws && message) { 851 | console.log("sending message: ", message); 852 | this.ws.send(JSON.stringify(message)); 853 | } 854 | }, 855 | 856 | _destroy: function () { 857 | if (this.ws) { 858 | this.ws = null; 859 | } 860 | }, 861 | 862 | _handleP2PMessage: function (message) { 863 | var payload = message.payload; 864 | if (!payload) { 865 | return; 866 | } 867 | 868 | console.log('processing message payload ' + JSON.stringify(payload)); 869 | this.emit('message', {from: message.from, message: message.payload}); 870 | }, 871 | 872 | _handleP2PJoinRequest: function (message) { 873 | var payload = message.payload; 874 | if (!payload) { 875 | return; 876 | } 877 | 878 | this._send({ 879 | type: "p2p.join-response", 880 | payload: { 881 | allowJoin: true, 882 | requestId: payload.requestId 883 | } 884 | }); 885 | }, 886 | 887 | _handleP2PJoin: function (message) { 888 | var client = message.client; 889 | if (!client) { 890 | return; 891 | } 892 | 893 | console.log('processing client join ' + JSON.stringify(client)); 894 | this.emit(ConnectManager.EventType.JOIN, client); 895 | }, 896 | 897 | _handleP2PDepart: function (message) { 898 | var clientId = message.from; 899 | if (!clientId) { 900 | return; 901 | } 902 | 903 | console.log('processing client departure ' + clientId); 904 | this.emit(ConnectManager.EventType.DEPART, { id: clientId }); 905 | } 906 | }); 907 | 908 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////// 909 | // Helpers 910 | 911 | function getParameterByName(name) { 912 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 913 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 914 | results = regex.exec(location.search); 915 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 916 | } 917 | 918 | // very simple class maker 919 | function createClass(desc) { 920 | var constructor; 921 | 922 | if (desc.constructor) { 923 | constructor = desc.constructor; 924 | delete desc.constructor; 925 | } else { 926 | constructor = function () {}; 927 | throw new Error("no constructor"); 928 | } 929 | 930 | var prototype = constructor.prototype; 931 | 932 | if (desc.mixins) { 933 | desc.mixins.forEach(function (mixin) { 934 | extend(prototype, mixin); 935 | }); 936 | delete desc.mixins; 937 | } 938 | 939 | if (desc.statics) { 940 | extend(constructor, desc.statics); 941 | delete desc.statics; 942 | } 943 | 944 | extend(prototype, desc); 945 | return constructor; 946 | } 947 | 948 | function extend(a, b) { 949 | for (var key in b) { 950 | if (b.hasOwnProperty(key) && !a.hasOwnProperty(key)) { 951 | a[key] = b[key] 952 | } 953 | } 954 | return a; 955 | } 956 | 957 | function nop() {} 958 | 959 | return { 960 | ConnectManager: ConnectManager, 961 | WebOSAppChannels: WebOSAppChannels 962 | } 963 | })(); -------------------------------------------------------------------------------- /tools/license_header.txt: -------------------------------------------------------------------------------- 1 | // 2 | // Connect SDK JavaScript Bridge 3 | // Version 1.3.1 Date: 11 Jun 2014 2:22 PM 4 | // 5 | // Created by Jeremy White on 4/16/14. 6 | // Copyright (c) 2014 LG Electronics. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | -------------------------------------------------------------------------------- /tools/minify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Usage (Linux/OS X)): 4 | # 0. cd tools 5 | # 1. npm install -g minifier 6 | # 2. chmod a+x ./minify.sh 7 | # 3. ./minify.sh 8 | 9 | # copy uncompressed JS file and apply header 10 | cat ./license_header.txt > ../dist/connect_bridge.js 11 | cat ../src/connect_bridge.js >> ../dist/connect_bridge.js 12 | 13 | # set up minified JS file with header 14 | cat ./license_header.txt > ../dist/connect_bridge.min.js 15 | 16 | # minify JS file to tmp location 17 | minify --output /tmp/connect_bridge.min.js ../src/connect_bridge.js 18 | 19 | # copy minified JS file contents to min file 20 | cat /tmp/connect_bridge.min.js >> ../dist/connect_bridge.min.js 21 | 22 | # copy minified JS file to sample 23 | cp ../dist/connect_bridge.min.js ../sample/scripts 24 | 25 | # cleanup 26 | rm /tmp/connect_bridge.min.js --------------------------------------------------------------------------------