├── AndroidRTC
├── images
│ ├── logo.png
│ ├── camera.png
│ ├── loader.gif
│ ├── settings.png
│ ├── microphone.png
│ ├── search-user.png
│ ├── send-message.png
│ └── share-files.png
├── fonts
│ ├── MyriadPro-Bold.otf
│ ├── MyriadPro-Light.otf
│ └── MyriadPro-Regular.otf
├── scripts
│ ├── common-signaling.js
│ ├── ui-handler.js
│ ├── conversation.js
│ └── FileBufferReader.js
├── manifest.json
├── styles
│ └── ui-styles.css
└── index.html
├── demos
├── common-signaling.js
├── common-styles.css
├── search-user.html
└── cross-language-chat.html
├── package.json
├── server.js
├── README.md
└── conversation.js
/AndroidRTC/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/logo.png
--------------------------------------------------------------------------------
/AndroidRTC/images/camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/camera.png
--------------------------------------------------------------------------------
/AndroidRTC/images/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/loader.gif
--------------------------------------------------------------------------------
/AndroidRTC/images/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/settings.png
--------------------------------------------------------------------------------
/AndroidRTC/images/microphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/microphone.png
--------------------------------------------------------------------------------
/AndroidRTC/fonts/MyriadPro-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/fonts/MyriadPro-Bold.otf
--------------------------------------------------------------------------------
/AndroidRTC/images/search-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/search-user.png
--------------------------------------------------------------------------------
/AndroidRTC/images/send-message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/send-message.png
--------------------------------------------------------------------------------
/AndroidRTC/images/share-files.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/images/share-files.png
--------------------------------------------------------------------------------
/AndroidRTC/fonts/MyriadPro-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/fonts/MyriadPro-Light.otf
--------------------------------------------------------------------------------
/AndroidRTC/fonts/MyriadPro-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muaz-khan/Conversation.js/master/AndroidRTC/fonts/MyriadPro-Regular.otf
--------------------------------------------------------------------------------
/demos/common-signaling.js:
--------------------------------------------------------------------------------
1 | var SIGNALING_SERVER = 'wss://webrtcweb.com:9449/';
2 | var channel = window.RMCDefaultChannel;
3 |
4 | var websocket = new WebSocket(SIGNALING_SERVER);
5 |
6 | websocket.push = websocket.send;
7 | websocket.send = function(data) {
8 | websocket.push(JSON.stringify({
9 | channel: channel,
10 | data: data
11 | }));
12 | };
13 |
14 | var signaler = new Signaler();
15 | signaler.on('message', function(data) {
16 | websocket.send(data);
17 | });
18 |
19 | websocket.onmessage = function(event) {
20 | var data = JSON.parse(event.data);
21 | signaler.emit('message', data);
22 | };
23 |
24 | websocket.onopen = function(){
25 | console.info('WebSocket connection is opened');
26 |
27 | websocket.send({
28 | channel: channel
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/AndroidRTC/scripts/common-signaling.js:
--------------------------------------------------------------------------------
1 | var SIGNALING_SERVER = 'wss://webrtcweb.com:9449/';
2 | var channel = window.RMCDefaultChannel;
3 |
4 | var websocket = new WebSocket(SIGNALING_SERVER);
5 |
6 | websocket.push = websocket.send;
7 | websocket.send = function(data) {
8 | websocket.push(JSON.stringify({
9 | channel: channel,
10 | data: data
11 | }));
12 | };
13 |
14 | var signaler = new Signaler();
15 | signaler.on('message', function(data) {
16 | websocket.send(data);
17 | });
18 |
19 | websocket.onmessage = function(event) {
20 | var data = JSON.parse(event.data);
21 | signaler.emit('message', data);
22 | };
23 |
24 | websocket.onopen = function(){
25 | console.info('WebSocket connection is opened');
26 |
27 | websocket.send({
28 | channel: channel
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/AndroidRTC/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AndroidRTC",
3 | "description": "AndroidRTC is using WebRTC standards to bring realtime [secure] p2p connections!",
4 | "version": "0.0.0.3",
5 | "app": {
6 | "main": {
7 | "scripts": [
8 | "scripts/socket.io.js",
9 | "scripts/RTCMultiConnection.js",
10 | "scripts/FileBufferReader.js",
11 | "scripts/conversation.js",
12 | "scripts/common-signaling.js",
13 | "scripts/ui-handler.js",
14 | "styles/ui-styles.css",
15 | "fonts/MyriadPro-Bold.otf",
16 | "fonts/MyriadPro-Light.otf",
17 | "fonts/MyriadPro-Regular.otf",
18 | "images/camera.png",
19 | "images/loader.gif",
20 | "images/logo.png",
21 | "images/microphone.png",
22 | "images/search-user.png",
23 | "images/send-message.png",
24 | "images/settings.png",
25 | "images/share-files.png"
26 | ]
27 | },
28 | "launch":{
29 | "local_path": "index.html"
30 | }
31 | },
32 | "icons": {
33 | "128": "images/logo.png"
34 | }
35 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "conversationjs",
3 | "preferGlobal": true,
4 | "version": "1.0.0",
5 | "author": {
6 | "name": "Muaz Khan",
7 | "email": "muazkh@gmail.com",
8 | "url": "http://www.muazkhan.com/"
9 | },
10 | "description": "Conversation.js is inspired by skype; and it provides simple events-like API to manage conversations, enable/disable media devices; add/download files; and do anything supported by Skype.",
11 | "scripts": {
12 | "start": "node conversation.js"
13 | },
14 | "main": "./conversation.js",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/muaz-khan/Conversation.git"
18 | },
19 | "keywords": [
20 | "webrtc",
21 | "conversation",
22 | "conversation.js",
23 | "conversationjs",
24 | "rtcmulticonnection",
25 | "webrtc-library",
26 | "library",
27 | "javascript",
28 | "chrome",
29 | "firefox",
30 | "opera",
31 | "ie",
32 | "internet-explorer",
33 | "android",
34 | "rtcweb",
35 | "rtcmulticonnection.js",
36 | "multirtc",
37 | "webrtc-experiment",
38 | "javascript-library",
39 | "muaz",
40 | "muaz-khan"
41 | ],
42 | "analyze": false,
43 | "license": "MIT",
44 | "readmeFilename": "README.md",
45 | "bugs": {
46 | "url": "https://github.com/muaz-khan/Conversation/issues",
47 | "email": "muazkh@gmail.com"
48 | },
49 | "homepage": "https://www.webrtc-experiment.com/Conversationjs/",
50 | "_id": "conversationjs@1.0.0",
51 | "_from": "conversationjs@"
52 | }
53 |
--------------------------------------------------------------------------------
/demos/common-styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-user-select: none;
3 | -o-user-select: none;
4 | -ms-user-select: none;
5 | -moz-user-select: none;
6 | user-select: none;
7 |
8 | font-family: Calibri;
9 | font-family: 'Segoe UI Light', 'Segoe UI';
10 | font-size: 1.1em;
11 | font-weight: normal;
12 | }
13 |
14 | html {
15 | background: #eee;
16 | }
17 |
18 | body {
19 | font-size: 1.2em;
20 | line-height: 1.2em;
21 | margin: 0;
22 | }
23 |
24 | input[type=text], input[type=search] {
25 | background: none repeat scroll 0 0 #F9F9F9;
26 | border: 1px solid #CCCCCC;
27 | border-radius: 0 0 5px 5px;
28 | border-top: 0;
29 | font: 300 18px/40px'light', inherit;
30 | height: 40px;
31 | letter-spacing: 1px;
32 | margin-bottom: 4px;
33 | padding: 5px 10px;
34 | width: 100%;
35 | }
36 |
37 | #output {
38 | background: none repeat scroll 0 0 #F9F9F9;
39 | border: 1px solid #CCCCCC;
40 | border-radius: 5px 5px 0 0;
41 | font: 300 18px/40px'light', 'Helvetica Neue', Arial, Helvetica, sans-serif;
42 | height: 400px;
43 | letter-spacing: 1px;
44 | margin-bottom: 0;
45 | overflow: scroll;
46 | padding: 5px 10px;
47 | }
48 |
49 | #output div {
50 | border-bottom: 1px solid #CCCCCC;
51 | }
52 |
53 | section.name {
54 | float: left;
55 | overflow: hidden;
56 | padding-left: 2em;
57 | padding-right: 1em;
58 | text-align: right;
59 | width: 7em;
60 | height: 35px;
61 | }
62 |
63 | section.message {
64 | border-left: 1px solid #CCCCCC;
65 | margin-left: 10em;
66 | overflow: hidden;
67 | padding-left: 1em;
68 | }
69 |
70 | input, .message {
71 | -webkit-user-select: initial;
72 | -o-user-select: initial;
73 | -ms-user-select: initial;
74 | -moz-user-select: initial;
75 | user-select: initial;
76 | }
77 |
78 | button[disabled], input[disabled] {
79 | background: rgba(216, 205, 205, 0.2);
80 | border: 1px solid rgb(233, 224, 224);
81 | }
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // http://127.0.0.1:9001
2 | // http://localhost:9001
3 |
4 | var server = require('http'),
5 | url = require('url'),
6 | path = require('path'),
7 | fs = require('fs');
8 |
9 | function serverHandler(request, response) {
10 | var uri = url.parse(request.url).pathname,
11 | filename = path.join(process.cwd(), uri);
12 |
13 | fs.exists(filename, function(exists) {
14 | if (!exists) {
15 | response.writeHead(404, {
16 | 'Content-Type': 'text/plain'
17 | });
18 | response.write('404 Not Found: ' + filename + '\n');
19 | response.end();
20 | return;
21 | }
22 |
23 | if (filename.indexOf('favicon.ico') !== -1) {
24 | return;
25 | }
26 |
27 | var isWin = !!process.platform.match(/^win/);
28 |
29 | if (fs.statSync(filename).isDirectory() && !isWin) {
30 | filename += '/index.html';
31 | } else if (fs.statSync(filename).isDirectory() && !!isWin) {
32 | filename += '\\index.html';
33 | }
34 |
35 | fs.readFile(filename, 'binary', function(err, file) {
36 | if (err) {
37 | response.writeHead(500, {
38 | 'Content-Type': 'text/plain'
39 | });
40 | response.write(err + '\n');
41 | response.end();
42 | return;
43 | }
44 |
45 | var contentType;
46 |
47 | if (filename.indexOf('.html') !== -1) {
48 | contentType = 'text/html';
49 | }
50 |
51 | if (filename.indexOf('.js') !== -1) {
52 | contentType = 'application/javascript';
53 | }
54 |
55 | if (contentType) {
56 | response.writeHead(200, {
57 | 'Content-Type': contentType
58 | });
59 | } else response.writeHead(200);
60 |
61 | response.write(file, 'binary');
62 | response.end();
63 | });
64 | });
65 | }
66 |
67 | var app;
68 |
69 | app = server.createServer(serverHandler);
70 |
71 | app = app.listen(process.env.PORT || 9001, process.env.IP || "0.0.0.0", function() {
72 | var addr = app.address();
73 | console.log("Server listening at", addr.address + ":" + addr.port);
74 | });
75 |
--------------------------------------------------------------------------------
/AndroidRTC/styles/ui-styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Myriad';
3 | src: url('../fonts/MyriadPro-Light.otf') format("opentype");
4 | font-weight:400;
5 | }
6 |
7 | * {
8 | margin:0;
9 | padding: 0;
10 | color: black;
11 | font-family:Myriad, Arial, Verdana;
12 |
13 | -webkit-user-select: none;
14 | -moz-user-select: none;
15 | -o-user-select: none;
16 | -ms-user-select: none;
17 | user-select: none;
18 |
19 | -webkit-user-drag: none;
20 | -moz-user-drag: none;
21 | -o-user-drag: none;
22 | -ms-user-drag: none;
23 | user-drag: none;
24 | }
25 |
26 | textarea, input, .message {
27 | -webkit-user-select: initial;
28 | -moz-user-select: initial;
29 | -o-user-select: initial;
30 | -ms-user-select: initial;
31 | user-select: initial;
32 | }
33 |
34 | body {
35 | background: white;
36 | }
37 |
38 | header {
39 | background: rgb(245, 243, 243);
40 | border-bottom: 1px solid rgb(77, 72, 72);
41 | }
42 |
43 | .logo {
44 | width: 100px;
45 | height: 100px;
46 | vertical-align: middle;
47 | }
48 |
49 | .search-box {
50 | border: 1px solid rgb(158, 157, 157);
51 | display: inline-block;
52 | vertical-align: middle;
53 | width: 50%;
54 | overflow: hidden;
55 | background: white;
56 | }
57 |
58 | .search-box input {
59 | border:0;
60 | padding: 4px 4px;
61 | outline: none;
62 | font-size: 30px;
63 | background: transparent;
64 | color: black;
65 | width: 100%;
66 | }
67 |
68 | .icon {
69 | background-repeat:no-repeat;
70 | background-position: center center;
71 | width: 48px;
72 | height: 48px;
73 | vertical-align: middle;
74 | background-color: rgb(233, 231, 231);
75 | border: 1px solid rgb(151, 147, 147);
76 | }
77 |
78 | .icon:hover {
79 | background-color: rgb(233, 231, 201);
80 | }
81 |
82 | .icon:active {
83 | background-color: rgb(233, 231, 191);
84 | }
85 |
86 | .search-user {
87 | background-image: url('../images/search-user.png');
88 | position: absolute;
89 | margin-top: 26px;
90 | margin-left: -47px;
91 | }
92 |
93 | .setttings {
94 | background-image: url('../images/settings.png');
95 | }
96 |
97 | .microphone {
98 | background-image: url('../images/microphone.png');
99 | }
100 |
101 | .camera {
102 | background-image: url('../images/camera.png');
103 | }
104 |
105 | .share-files {
106 | background-image: url('../images/share-files.png');
107 | }
108 |
109 | .conversation-panel {
110 | border: 1px solid rgb(158, 157, 157);
111 | margin: 10px;
112 | }
113 |
114 | .conversation {
115 | border-top: 1px solid rgb(158, 157, 157);
116 | margin-top: -1px;
117 | }
118 |
119 | .activist-name {
120 | display: inline-block;
121 | border-right: 1px solid rgb(158, 157, 157);
122 | vertical-align: middle;
123 | text-align: center;
124 | padding: 8px 4px;
125 | width: 70px;
126 | overflow: hidden;
127 | border-top: 1px solid rgb(158, 157, 157);
128 | margin-top: -1px;
129 | }
130 |
131 | .activist-name .symbol {
132 | font-size: 30px;
133 | font-weight: bolder;
134 | text-decoration: underline;
135 | }
136 |
137 | .activist-name .full-name {
138 | }
139 |
140 | .conversation .message {
141 | display: inline-block;
142 | padding: 8px 4px;
143 | vertical-align: top;
144 | max-width: 90%;
145 | border-left: 1px solid rgb(158, 157, 157);
146 | margin-left: -1px;
147 | padding-left: 10px;
148 | }
149 | .conversation .message button {
150 | font-size: 13px;
151 | padding: 0 7px;
152 | height: 16px;
153 | cursor: pointer;
154 | background: rgb(253, 253, 255);
155 | border: 1px solid rgb(226, 43, 43);
156 | border-radius: 2px;
157 | margin: 0 5px;
158 | }
159 |
160 | .conversation .message button[disabled] {
161 | cursor: default;
162 | background: transparent;
163 | border: 1px solid rgb(219, 208, 208);
164 | border-radius: 0;
165 | color: rgb(219, 208, 208);
166 | }
167 |
168 | .conversation .message input {
169 | color: black;
170 | border: 1px solid rgb(139, 131, 131);
171 | background: white;
172 | padding: 0 5px;
173 | }
174 |
175 | .conversation .message-date {
176 | font-size: 12px;
177 | color: gray;
178 | margin-top: 5px;
179 | }
180 |
181 | footer {
182 | position: fixed;
183 | bottom: 0;
184 | width: 100%;
185 | border: 1px solid rgb(158, 157, 157);
186 | background: white;
187 | border: 1px solid rgb(77, 72, 72);
188 | }
189 |
190 | .chat-input {
191 | border-right: 1px solid rgb(158, 157, 157);
192 | display: inline-block;
193 | vertical-align: middle;
194 | width: 85%;
195 | overflow: hidden;
196 | }
197 |
198 | .chat-input textarea {
199 | border:0;
200 | padding: 4px 4px;
201 | outline: none;
202 | font-size: 20px;
203 | background: transparent;
204 | resize: none;
205 | width: 99%;
206 | }
207 |
208 | footer button {
209 | background-image: url('../images/send-message.png');
210 | }
211 |
212 | .slogan {
213 | position: absolute;
214 | margin-top: 8px;
215 | font-size: 14px;
216 | font-style: italic;
217 | height: 16px;
218 | overflow: hidden;
219 | width: 250px;
220 | }
221 |
222 | .media-panel {
223 | margin-left: 10%;
224 | display: none;
225 | margin-bottom: 1em;
226 | }
227 |
228 | .media-panel #hide-section {
229 | width: auto;
230 | padding: 0 7px;
231 | background: rgb(243, 80, 80);
232 | border: 1px solid rgb(162, 12, 12);
233 | height: 23px;
234 | color: white;
235 | cursor: pointer;
236 | border-radius: 3px
237 | }
238 |
239 | .media-panel video {
240 | width: 22%;
241 | }
242 |
243 | @media all and (max-width: 650px) {
244 | .conversation .message {
245 | max-width: 80%;
246 | }
247 | .chat-input {
248 | width: 90%;
249 | }
250 | .search-box {
251 | width: 45%;
252 | }
253 | }
254 |
255 | @media all and (max-width: 500px) {
256 | .conversation .message {
257 | max-width: 70%;
258 | }
259 | .chat-input {
260 | width: 80%;
261 | }
262 | .search-box {
263 | width: 35%;
264 | }
265 | }
266 |
267 | @media all and (max-width: 330px) {
268 | .conversation .message {
269 | max-width: 60%;
270 | }
271 | .chat-input {
272 | width: 70%;
273 | }
274 | .search-box {
275 | width: 30%;
276 | }
277 | }
278 |
279 | @media all and (max-width: 270px) {
280 | .conversation .message {
281 | max-width: 50%;
282 | }
283 | .chat-input {
284 | width: 50%;
285 | }
286 | .search-box {
287 | width: 25%;
288 | }
289 | }
290 | @media all and (max-width: 240px) {
291 | .conversation .message {
292 | max-width: 40%;
293 | }
294 | .chat-input {
295 | width: 40%;
296 | }
297 | .search-box {
298 | width: 20%;
299 | }
300 | }
301 |
302 | @media all and (max-width: 200px) {
303 | .conversation .message {
304 | max-width: 30%;
305 | }
306 | .chat-input {
307 | width: 30%;
308 | }
309 | .search-box {
310 | width: 15%;
311 | }
312 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## [Conversation.js](https://github.com/muaz-khan/Conversation.js) runs top over [RTCMultiConnection.js](http://www.RTCMultiConnection.org/) [](https://npmjs.org/package/conversationjs) [](https://npmjs.org/package/conversationjs)
2 |
3 | Conversation.js is inspired by skype; and it provides simple events-like API to manage conversations, enable/disable media devices; add/download files; and do anything supported by Skype.
4 |
5 | It allows you open data conversation between two or more users using their user-ids.
6 |
7 | It is MIT Licenced, which means that you can use it in any commercial/non-commercial product, free of cost.
8 |
9 | ```
10 | npm install conversationjs
11 | ```
12 |
13 | To use it:
14 |
15 | ```htm
16 |
17 | ```
18 |
19 | ## [Demos](https://www.webrtc-experiment.com/Conversationjs/) using [Conversation.js](https://github.com/muaz-khan/Conversation.js)
20 |
21 |
22 | -
23 | AndroidRTC
24 |
25 |
26 | -
27 | Search Users
28 |
29 |
30 | -
31 | Cross-Language (Multi-Lingual) Text Chat
32 |
33 |
34 | -
35 | Old Conversation.js demos
36 |
37 |
38 |
39 | ## Gif Presentation
40 |
41 | * https://cdn.webrtc-experiment.com/images/AndroidRTC.gif
42 |
43 | ## Link the library
44 |
45 | ```html
46 |
47 |
48 | ```
49 |
50 | =
51 |
52 | ## Open simple conversation between two users
53 |
54 | ```javascript
55 | var websocket = new WebSocket('ws://domain:port/');
56 |
57 | // initializing constructor
58 | var signaler = new Signaler();
59 |
60 | // whatever sent from conversation.js
61 | signaler.on('message', function(message) {
62 | // here, you received message from conversation.js
63 | // send message over WebSocket connection
64 | websocket.send(JSON.stringify(message));
65 | });
66 |
67 | // your websocket listener that subscribes
68 | // for all messages broadcasted from WebSockets connection
69 | websocket.onmessage = function(event) {
70 | var message = JSON.parse(event.data);
71 |
72 | // here, you received a message from websocket server
73 | // pass message to conversation.js
74 | signaler.emit('message', message);
75 | });
76 |
77 | var user = new User();
78 |
79 | // connect user to signaler
80 | signaler.connect(user);
81 |
82 | // invoke this method to open conversation with any user
83 | user.openconversationwith('target-username');
84 |
85 | // this event is fired when conversation is opened
86 | user.on('conversation-opened', function (conversation) {
87 | // conversation.targetuser
88 |
89 | // emit a message to target user
90 | // conversation.emit('message', 'hello there');
91 |
92 | conversation.on('message', function (event) {
93 | console.log(event.username, event.data);
94 | });
95 |
96 | // enable your microphone and tell target user about it; he can
97 | // also enable his microphone or he can simply listen your voice!
98 | // conversation.emit('enable', 'microphone');
99 |
100 | conversation.on('media-enabled', function (media) {
101 | // media.type == 'audio' || 'video' || 'screen'
102 | // media.hasmicrophone == true || null
103 | // media.hascamera == true || null
104 | // media.hasscreen == true || null
105 | // media.sender == 'string'
106 |
107 | media.emit('join-with', 'microphone');
108 | });
109 | });
110 | ```
111 |
112 | ## How to use socket.io?
113 |
114 | ```javascript
115 | var socket = io.connect();
116 |
117 | // initializing constructor
118 | var signaler = new Signaler();
119 |
120 | // whatever sent from conversation.js
121 | signaler.on('message', function(message) {
122 | // here, you received message from conversation.js
123 | // pass/emit message to node.js
124 | socket.emit('message', message);
125 | });
126 |
127 | // your socket.io listener that subscribes
128 | // for all messages broadcasted from Node.js
129 | socket.on('message', function(message) {
130 | // here, you received a message from node.js server
131 | // pass message to conversation.js
132 | signaler.emit('message', message);
133 | });
134 |
135 | // connect user to signaler
136 | signaler.connect(user);
137 | ```
138 |
139 | ## How to use WebSockets?
140 |
141 | ```javascript
142 | var websocket = new WebSocket('ws://domain:port/');
143 |
144 | // initializing constructor
145 | var signaler = new Signaler();
146 |
147 | // whatever sent from conversation.js
148 | signaler.on('message', function(message) {
149 | // here, you received message from conversation.js
150 | // send message over WebSocket connection
151 | websocket.send(JSON.stringify(message));
152 | });
153 |
154 | // your websocket listener that subscribes
155 | // for all messages broadcasted from WebSockets connection
156 | websocket.onmessage = function(event) {
157 | var message = JSON.parse(event.data);
158 |
159 | // here, you received a message from websocket server
160 | // pass message to conversation.js
161 | signaler.emit('message', message);
162 | });
163 |
164 | // connect user to signaler
165 | signaler.connect(user);
166 | ```
167 |
168 | ## How to set defaults?
169 |
170 | "defaults" are default properties, objects and methods that are applied to RTCMultiConnection object.
171 |
172 | See list of all such properties here: http://www.rtcmulticonnection.org/docs/
173 |
174 | ```javascript
175 | user.defaults = {
176 | log: true, // for production use only.
177 | trickleIce: true, // for SIP/XMPP and XHR
178 | getExternalIceServers: false, // ice-servers from xirsys.com
179 | leaveOnPageUnload: true,
180 | iceServers: [{
181 | url: 'stun:stun.l.google.com:19302'
182 | }],
183 | iceProtocols: {
184 | tcp: true,
185 | udp: true
186 | },
187 | candidates: {
188 | host: true, // local/host candidates
189 | reflexive: true, // STUN candidates
190 | relay: true // TURN candidates
191 | },
192 | autoReDialOnFailure: false, // renegotiation will not work if it is true
193 | body: document.body || document.documentElement
194 | };
195 | ```
196 |
197 | ## How to accept/reject friend requests?
198 |
199 | ```javascript
200 | user.on('friend-request', function (request) {
201 | if (window.confirm('Do you want to accept friend-request made by ' + request.sender + '?')) {
202 | request.accept();
203 | } else {
204 | request.reject();
205 | }
206 | });
207 | ```
208 |
209 | ## How to check friend-request status?
210 |
211 | ```javascript
212 | user.on('request-status', function (request) {
213 | if (request.status == 'accepted') {
214 | alert(request.sender + ' accepted your request.');
215 | }
216 | if (request.status == 'rejected') {
217 | alert(request.sender + ' rejected your request.');
218 | }
219 | });
220 | ```
221 |
222 | ## How to emit events to multiple users?
223 |
224 | ```javascript
225 | document.querySelector('#chat-message').onchange = function (event) {
226 | user.peers.emit('message', this.value);
227 | };
228 |
229 | document.querySelector('#enable-microphone').onclick = function () {
230 | user.peers.emit('enable', 'microphone');
231 | };
232 |
233 | document.querySelector('#enable-camera').onclick = function () {
234 | user.peers.emit('enable', 'camera');
235 | };
236 |
237 | document.querySelector('#enable-screen').onclick = function () {
238 | user.peers.emit('enable', 'screen');
239 | };
240 | ```
241 |
242 | ## How to share files?
243 |
244 | ```javascript
245 | document.querySelector('input[type=file]').onchange = function () {
246 | user.peers.emit('add-file', this.files);
247 | };
248 | ```
249 |
250 | ## How to check if target user added file?
251 |
252 | ```javascript
253 | conversation.on('add-file', function (file) {
254 | file.download();
255 |
256 | // or file.cancel();
257 | });
258 | ```
259 |
260 | ## How to check file-download progress?
261 |
262 | ```javascript
263 | conversation.on('file-progress', function (progress) {
264 | console.log('percentage %', progress.percentage);
265 | // progress.file.name
266 | // progress.sender
267 | });
268 | ```
269 |
270 | ## How to save downloaded file to disk?
271 |
272 | ```javascript
273 | conversation.on('file-downloaded', function (file) {
274 | // file.sender
275 | file.savetodisk();
276 | });
277 | ```
278 |
279 | ## How to check if file is successfully sent?
280 |
281 | ```javascript
282 | conversation.on('file-sent', function (file) {
283 | // file.sender
284 | console.log(file.name, 'sent.');
285 | });
286 | ```
287 |
288 | ## How to check if target user refused to receive your file?
289 |
290 | ```javascript
291 | conversation.on('file-cancelled', function (file) {
292 | // file.sender
293 | console.log(file.name, 'cancelled.');
294 | });
295 | ```
296 |
297 | ## Credits
298 |
299 | [Muaz Khan](https://github.com/muaz-khan):
300 |
301 | 1. Personal Webpage: http://www.muazkhan.com
302 | 2. Email: muazkh@gmail.com
303 | 3. Twitter: https://twitter.com/muazkh and https://twitter.com/WebRTCWeb
304 | 4. Google+: https://plus.google.com/+WebRTC-Experiment
305 | 5. Facebook: https://www.facebook.com/WebRTC
306 |
307 | ## License
308 |
309 | [Conversation.js](https://github.com/muaz-khan/Conversation.js) is released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](https://plus.google.com/+MuazKhan).
310 |
--------------------------------------------------------------------------------
/AndroidRTC/scripts/ui-handler.js:
--------------------------------------------------------------------------------
1 | // ui-handler for AndroidRTC app
2 | function searchInDOM(selector) {
3 | return document.querySelector(selector);
4 | }
5 |
6 | function getFromLocalStorage(key) {
7 | return localStorage.getItem(key);
8 | }
9 |
10 | function putIntoLocalStorage(key, value) {
11 | return localStorage.setItem(key, value);
12 | }
13 |
14 | window.addEventListener('DOMContentLoaded', function() {
15 | // buttons
16 | var btnSendChatMessage = searchInDOM('.send-chat-message');
17 | var btnSearchUser = searchInDOM('.search-user');
18 | var btnSettings = searchInDOM('.setttings');
19 |
20 | // input boxes
21 | var chatInputBox = searchInDOM('.chat-input textarea');
22 | var inputSearchFriends = searchInDOM('#search-friends');
23 |
24 | var messageSound = document.getElementById('message-sound');
25 |
26 | // containers
27 | var conversationPanel = searchInDOM('.conversation-panel');
28 |
29 | // Conversation.js
30 | window.user = new User();
31 |
32 | // a custom method used to append a new DIV into DOM
33 | appendConversation({
34 | username: 'Welcome',
35 | message: 'Your username is: You can modify & share it with your friends.',
36 | callback: function(parent) {
37 | parent.querySelector('input[type=text]').onkeyup = function() {
38 | user.username = this.value;
39 | };
40 | }
41 | });
42 |
43 | // these are RTCMultiConnection.js defaults
44 | // RTCMultiConnection.js is big-javascript library
45 | // RTCMultiConnection provides tons of WebRTC features!
46 | // read more here: www.RTCMultiConnection.org/docs/
47 | user.defaults = {
48 | log: false, // for production use only.
49 | trickleIce: true, // for SIP/XMPP and XHR
50 | getExternalIceServers: false, // ice-servers from xirsys.com
51 | leaveOnPageUnload: true,
52 | /*iceServers: [{
53 | url: 'stun:stun.l.google.com:19302'
54 | }],*/
55 | iceProtocols: {
56 | tcp: true,
57 | udp: true
58 | },
59 | candidates: {
60 | host: true, // local/host candidates
61 | reflexive: true, // STUN candidates
62 | relay: true // TURN candidates
63 | },
64 | autoReDialOnFailure: true, // renegotiation will not work if it is true
65 | body: document.getElementById('media-container')
66 | };
67 |
68 | // optional method to display the logs
69 | user.on('log', function(message) {
70 | console.log(message);
71 | appendConversation({
72 | username: 'Dev-Logs',
73 | message: message.replace(/\\r\\n/g, '
')
74 | });
75 | });
76 |
77 | // "signaler" object is defined in "common-signaling.js"
78 | // this method connects users to signaling-channel
79 | // so that user can call/search/invite any other user
80 | // also, other users can search him.
81 | signaler.connect(user);
82 |
83 | if (getFromLocalStorage('username')) {
84 | user.username = localStorage.getItem('username');
85 | } else putIntoLocalStorage('username', user.username);
86 |
87 | user.on('conversation-opened', function(conversation) {
88 | conversation.emit('message', 'Hey ' + conversation.targetuser + ', conversation has been opened between you and ' + user.username + '.');
89 | conversation.on('message', function(event) {
90 | appendConversation({
91 | username: event.username,
92 | message: event.data
93 | });
94 | messageSound.play();
95 | });
96 | conversation.on('stream', function(stream) {
97 | user.emit('--log', 'stream: ' + stream.streamid);
98 |
99 | conversation.peer.body.insertBefore(stream.mediaElement, conversation.peer.body.firstChild);
100 |
101 | btnSettings.style.display = 'none';
102 | mediaPanel.style.display = 'block';
103 | });
104 |
105 | conversation.on('add-file', function(file) {
106 | appendConversation({
107 | username: 'File',
108 | message: file.sender + ' added a file: "' + file.name + '". ',
109 | callback: function(parent) {
110 | parent.id = file.uuid;
111 |
112 | parent.querySelector('#download').onclick = function() {
113 | file.download();
114 | disableBoth();
115 | };
116 | parent.querySelector('#cancel').onclick = function() {
117 | file.cancel();
118 | disableBoth();
119 | };
120 |
121 | function disableBoth() {
122 | parent.querySelector('#download').disabled = true;
123 | parent.querySelector('#cancel').disabled = true;
124 | }
125 | }
126 | });
127 | });
128 |
129 | conversation.on('file-downloaded', function(file) {
130 | var parent = document.getElementById(file.uuid);
131 | if (!parent) return;
132 |
133 | parent.querySelector('.message').innerHTML = file.sender + ' shared a file which is downloaded in the application: "' + file.name + '". ';
134 | parent.querySelector('#save-to-disk').onclick = function() {
135 | file.savetodisk();
136 | this.disabled = true;
137 | };
138 | });
139 |
140 | conversation.on('file-cancelled', function(file) {
141 | appendConversation({
142 | username: 'Cancelled',
143 | message: conversation.targetuser + ' cancelled your file: "' + file.name + '".'
144 | });
145 | });
146 |
147 | conversation.on('file-sent', function(file) {
148 | appendConversation({
149 | username: 'Sent',
150 | message: conversation.targetuser + ' received your file: "' + file.name + '".'
151 | });
152 | });
153 |
154 | conversation.on('file-progress', function(progress) {
155 | var parent = document.getElementById(progress.uuid);
156 | if (!parent || !parent.querySelector('#download')) return;
157 |
158 | parent.querySelector('#download').innerHTML = 'File progress percentage: ' + progress.percentage;
159 | });
160 | });
161 |
162 | // button used to search & invite a user
163 | btnSearchUser.onclick = function() {
164 | if (isWhiteSpace(inputSearchFriends.value)) return;
165 | user.emit('search', inputSearchFriends.value);
166 | inputSearchFriends.value = '';
167 | };
168 |
169 | inputSearchFriends.onkeyup = function(e) {
170 | if (e.keyCode == 13) btnSearchUser.onclick();
171 | };
172 |
173 | // this event is fired if a user is detected by search-agent.
174 | user.on('search', function(result) {
175 | appendConversation({
176 | username: 'System',
177 | message: result.username + ' is online. Inviting him for chat.'
178 | });
179 |
180 | // currently the only method to open conversation
181 | // between two users
182 | user.openconversationwith(result.username);
183 | });
184 |
185 | btnSendChatMessage.onclick = function() {
186 | if (isWhiteSpace(chatInputBox.value)) return;
187 | user.peers.emit('message', chatInputBox.value);
188 | appendConversation({
189 | username: user.username,
190 | message: chatInputBox.value
191 | });
192 | chatInputBox.value = '';
193 | };
194 |
195 | chatInputBox.onkeyup = function(e) {
196 | if (e.keyCode != 13) return;
197 | btnSendChatMessage.onclick();
198 | };
199 |
200 | var mediaPanel = document.querySelector('.media-panel');
201 | btnSettings.onclick = function() {
202 | btnSettings.style.display = 'none';
203 | mediaPanel.style.display = 'block';
204 | };
205 |
206 | document.querySelector('#hide-section').onclick = function() {
207 | btnSettings.style.display = 'inline';
208 | mediaPanel.style.display = 'none';
209 | };
210 |
211 | var btnEnableMicrophone = document.querySelector('.microphone');
212 | btnEnableMicrophone.onclick = function() {
213 | btnEnableMicrophone.style.display = 'none';
214 | user.peers.emit('enable', 'microphone');
215 | };
216 |
217 | var btnEnableCamera = document.querySelector('.camera');
218 | btnEnableCamera.onclick = function() {
219 | btnEnableCamera.style.display = 'none';
220 | user.peers.emit('enable', 'camera');
221 | };
222 |
223 | var btnShareFiles = document.querySelector('.share-files');
224 | btnShareFiles.onclick = function() {
225 | var fileSelector = new FileSelector();
226 | fileSelector.selectSingleFile(function(file) {
227 | user.peers.emit('add-file', file);
228 | });
229 | };
230 |
231 | function isWhiteSpace(str) {
232 | return str.replace(/^\s+|\s+$/g, '').length <= 0;
233 | }
234 |
235 | function appendConversation(args) {
236 | var conversation = document.createElement('div');
237 | conversation.className = 'conversation';
238 |
239 | var activistName = document.createElement('div');
240 | activistName.className = 'activist-name';
241 | conversation.appendChild(activistName);
242 |
243 | var symbol = document.createElement('div');
244 | symbol.className = 'symbol';
245 | symbol.innerHTML = args.username.split('')[0].toUpperCase();
246 | activistName.appendChild(symbol);
247 |
248 | var fullName = document.createElement('div');
249 | fullName.className = 'full-name';
250 | fullName.innerHTML = args.username;
251 | activistName.appendChild(fullName);
252 |
253 | var message = document.createElement('div');
254 | message.className = 'message';
255 | message.innerHTML = args.message;
256 | conversation.appendChild(message);
257 |
258 | var messageDate = document.createElement('div');
259 | messageDate.className = 'message-date';
260 | messageDate.innerHTML = getCurrentTime();
261 | message.appendChild(messageDate);
262 |
263 | conversationPanel.insertBefore(conversation, conversationPanel.firstChild);
264 |
265 | if (args.callback) args.callback(conversation);
266 | }
267 |
268 | function getCurrentTime() {
269 | var date = new Date();
270 | var hours = date.getHours();
271 | var minutes = date.getMinutes();
272 | var seconds = date.getSeconds();
273 |
274 | if (hours.toString().length == 1) hours = '0' + hours;
275 | if (minutes.toString().length == 1) minutes = '0' + minutes;
276 | if (seconds.toString().length == 1) seconds = '0' + seconds;
277 |
278 | return hours + ':' + minutes + ':' + seconds;
279 | }
280 | }, false);
281 |
--------------------------------------------------------------------------------
/AndroidRTC/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AndroidRTC: Runs top over Conversation.js!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/demos/search-user.html:
--------------------------------------------------------------------------------
1 | Search Users using Conversation.js ® Muaz Khan
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
109 |
110 |
111 |
211 |
212 |
219 |
--------------------------------------------------------------------------------
/demos/cross-language-chat.html:
--------------------------------------------------------------------------------
1 | Text-Translation (Multi-Lingual) using Conversation.js ® Muaz Khan
2 |
3 |
4 | Text-Translation (Multi-Lingual) using Conversation.js
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
118 |
119 |
120 |
212 |
213 |
220 |
--------------------------------------------------------------------------------
/conversation.js:
--------------------------------------------------------------------------------
1 | // Last time updated at Sep 15, 2014, 08:32:23
2 |
3 | // Latest file can be found here: https://cdn.webrtc-experiment.com/conversation.js
4 |
5 | // Muaz Khan - www.MuazKhan.com
6 | // MIT License - www.WebRTC-Experiment.com/licence
7 | // _______________
8 | // Conversation.js
9 |
10 | /*
11 | -. Signaler object added. See below for how to use it.
12 |
13 | var signaler = new Signaler();
14 | signaler.on('message', function(message) {
15 | socket.send(message);
16 | });
17 |
18 | socket.on('message', function(message) {
19 | signaler.emit('message', message);
20 | });
21 |
22 | var user = new User();
23 |
24 | signaler.connect(user);
25 | */
26 |
27 | (function() {
28 | window.Signaler = function() {
29 | var signaler = this;
30 | var users = [];
31 | signaler.connect = function(user) {
32 | user.signaler = signaler;
33 | user.emit('--signaler-connected');
34 | users.push(user);
35 | };
36 |
37 | signaler.send = function(data) {
38 | signaler.emit('--message', data);
39 | };
40 |
41 | signaler.events = {};
42 | signaler.on = function(event, callback) {
43 | signaler.events[event] = callback;
44 | };
45 |
46 | signaler.emit = function() {
47 | if (!arguments[0]) throw 'At least one argument is mandatory.';
48 |
49 | var internalevent = arguments[0].indexOf('--') == 0;
50 | if (!internalevent) {
51 | if (arguments[0] == 'message') {
52 | var message = arguments[1];
53 | users.forEach(function(user) {
54 | user.onsignalingmessage(message);
55 | });
56 | }
57 |
58 | return;
59 | }
60 |
61 | if (internalevent) {
62 | arguments[0] = arguments[0].replace('--', '');
63 | }
64 |
65 | if (!signaler.events[arguments[0]]) {
66 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
67 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
68 | console.warn(warning);
69 |
70 | } else signaler.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
71 | };
72 | };
73 |
74 | window.User = function(_defaults) {
75 | var user = this;
76 |
77 | user.randomstring = function() {
78 | // suggested by @rvulpescu from #154
79 | if (window.crypto && crypto.getRandomValues && navigator.userAgent.indexOf('Safari') == -1) {
80 | var a = window.crypto.getRandomValues(new Uint32Array(3)),
81 | token = '';
82 | for (var i = 0, l = a.length; i < l; i++) {
83 | token += a[i].toString(36);
84 | }
85 | return token;
86 | } else {
87 | return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
88 | }
89 | };
90 |
91 | user.defaults = {};
92 | user.username = user.randomstring();
93 | user.status = 'online';
94 |
95 | user.openconversationwith = function(targetuser) {
96 | var conversation = new Conversation(user, targetuser);
97 |
98 | // check who is online
99 | user.emit('signal', {
100 | whoisonline: true,
101 | responsefor: targetuser
102 | });
103 | };
104 |
105 | user.onmessagecallbacks = {};
106 | user.onsignalingmessage = function(message) {
107 | if (message.isrtcmulticonnectioninnermessage && user.onmessagecallbacks[message.channel]) {
108 | return user.onmessagecallbacks[message.channel](message.message);
109 | }
110 |
111 | if (message.searchuser) {
112 | if (user.status === 'online') {
113 | user.emit('signal', {
114 | useronline: true,
115 | responsefor: message.sender
116 | });
117 | }
118 | return;
119 | }
120 |
121 | if (message.useronline && message.responsefor == user.username) {
122 | user.emit('--search', {
123 | username: message.sender
124 | });
125 | return;
126 | }
127 |
128 | if (message.whoisonline && message.responsefor == user.username && user.status == 'online') {
129 | user.emit('--friend-request', {
130 | accept: function() {
131 | if (!user.peers[message.sender]) {
132 | var randomchannel = user.randomstring();
133 | user.emit('signal', {
134 | iamonline: true,
135 | responsefor: message.sender,
136 | randomchannel: randomchannel
137 | });
138 |
139 | var conversation = new Conversation(user, message.sender);
140 |
141 | conversation.addnewrtcmulticonnectionpeer({
142 | targetuser: message.sender,
143 | channel: randomchannel
144 | });
145 | }
146 |
147 | user.emit('signal', {
148 | requestaccepted: true,
149 | sender: user.username,
150 | responsefor: message.sender
151 | });
152 | },
153 | reject: function() {
154 | user.emit('signal', {
155 | requestrejected: true,
156 | sender: user.username,
157 | responsefor: message.sender
158 | });
159 | },
160 | sender: message.sender
161 | });
162 | }
163 |
164 | if (message.requestaccepted && message.responsefor == user.username) {
165 | user.emit('--request-status', {
166 | status: 'accepted',
167 | sender: message.sender
168 | });
169 | }
170 |
171 | if (message.requestrejected && message.responsefor == user.username) {
172 | user.emit('--request-status', {
173 | status: 'accepted',
174 | sender: message.sender
175 | });
176 | }
177 |
178 | if (message.iamonline && message.responsefor == user.username) {
179 | if (!user.peers[message.sender]) {
180 | user.conversations[message.sender].addnewrtcmulticonnectionpeer({
181 | targetuser: message.sender,
182 | channel: message.randomchannel,
183 | isInitiator: true
184 | });
185 | }
186 | }
187 |
188 | if (message.sessionDescription && message.responsefor == user.username) {
189 | user.peers[message.sender].join(message.sessionDescription);
190 | }
191 | }
192 |
193 | // reference to all RTCMultiConnection peers
194 | user.peers = {
195 | emit: function(eventName, value) {
196 | for (var conversation in user.conversations) {
197 | user.conversations[conversation].emit(eventName, value);
198 | }
199 | },
200 | length: 0
201 | };
202 |
203 | // reference to all Conversation objects
204 | user.conversations = {};
205 |
206 | // reference to all local media streams
207 | user.localstreams = {};
208 |
209 | user.events = {};
210 | user.on = function(event, callback) {
211 | user.events[event] = callback;
212 | };
213 |
214 | user.emit = function() {
215 | if (!arguments[0]) throw 'At least one argument is mandatory.';
216 |
217 | var internalevent = arguments[0].indexOf('--') == 0;
218 | if (!internalevent) {
219 | if (arguments[0] == 'search') {
220 | user.emit('signal', {
221 | searchuser: arguments[1]
222 | });
223 | }
224 |
225 | if (arguments[0] == 'signal') {
226 | var data = arguments[1];
227 | data.sender = user.username;
228 | user.signaler.emit('--message', data);
229 | }
230 |
231 | return;
232 | }
233 |
234 | if (internalevent) {
235 | arguments[0] = arguments[0].replace('--', '');
236 | if (arguments[0] == 'log' && user.defaults.log == false) return;
237 | }
238 |
239 | if (!user.events[arguments[0]]) {
240 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
241 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
242 | console.warn(warning);
243 |
244 | } else user.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
245 | };
246 |
247 | // defaults
248 | user.on('request-status', function(request) {
249 | user.emit('--log', request.sender + ' ' + request.status + ' your request.');
250 | });
251 |
252 | user.on('friend-request', function(request) {
253 | request.accept();
254 | });
255 |
256 | user.on('signaler-connected', function() {
257 | user.emit('--log', 'Signaling medium is ready for pub/sub.');
258 | });
259 |
260 | user.on('log', function(message) {
261 | console.log(message);
262 | });
263 |
264 | for (var data in _defaults || {}) {
265 | user.defaults[data] = _defaults[data];
266 | }
267 |
268 | if (_defaults) {
269 | user.emit('signaler', 'start');
270 | }
271 | };
272 |
273 | function Conversation(user, targetuser) {
274 | var conversation = this;
275 |
276 | function sendmessage(data) {
277 | conversation.peer.send(data);
278 | }
279 |
280 | function enablemicrophone() {
281 | if (user.localstreams.microphone) return;
282 |
283 | conversation.peer.captureUserMedia(function(stream) {
284 | user.localstreams.microphone = stream;
285 |
286 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
287 |
288 | conversation.peer.send({
289 | signalingmessage: true,
290 | hasmicrophone: true,
291 | streamavailable: true,
292 | sender: user.username,
293 | type: 'audio'
294 | });
295 | }, {
296 | audio: true
297 | });
298 | }
299 |
300 | function enablecamera() {
301 | if (user.localstreams.camera) return;
302 |
303 | conversation.peer.captureUserMedia(function(stream) {
304 | user.localstreams.camera = stream;
305 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
306 |
307 | conversation.peer.send({
308 | signalingmessage: true,
309 | hascamera: true,
310 | streamavailable: true,
311 | sender: user.username,
312 | type: 'video'
313 | });
314 | }, {
315 | audio: true,
316 | video: true
317 | });
318 | }
319 |
320 | function enablescreen() {
321 | if (user.localstreams.screen) return;
322 |
323 | conversation.peer.captureUserMedia(function(stream) {
324 | user.localstreams.screen = stream;
325 |
326 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
327 |
328 | conversation.peer.send({
329 | signalingmessage: true,
330 | hasscreen: true,
331 | streamavailable: true,
332 | sender: user.username,
333 | type: 'screen'
334 | });
335 | }, {
336 | screen: true
337 | });
338 | };
339 |
340 | conversation.attachie = {
341 | files: {},
342 | messages: {}
343 | };
344 |
345 | conversation.addnewrtcmulticonnectionpeer = function(args) {
346 | var connection = new RTCMultiConnection(args.channel);
347 |
348 | for (var d in user.defaults) {
349 | if (typeof connection[d] !== 'undefined' /* && typeof connection[d] !== 'function' */ ) {
350 | connection[d] = user.defaults[d];
351 | }
352 | }
353 |
354 | // workaround for RTCMultiConnection-v1.8 or older
355 | if (user.defaults.log == false) {
356 | connection.skipLogs();
357 | }
358 |
359 | connection.userid = user.username;
360 |
361 | // v1.9 and onwards supports "onlog" event.
362 | connection.onlog = function(message) {
363 | user.emit('--log', JSON.stringify(message, null, '\t'));
364 | };
365 |
366 | connection.session = {
367 | data: true
368 | };
369 |
370 | connection.onopen = function() {
371 | conversation.targetuser = args.targetuser;
372 | user.emit('--conversation-opened', conversation);
373 | };
374 |
375 | connection.onmessage = function(event) {
376 | if (event.data.signalingmessage) {
377 | var data = event.data;
378 |
379 | if (data.streamavailable) {
380 | data.emit = function(first, second) {
381 | if (first == 'join-with' && second == 'nothing') {
382 | preview();
383 | }
384 |
385 | if (first == 'join-with' && second == 'microphone') {
386 | joinwithmicrophone();
387 | }
388 |
389 | if (first == 'join-with' && second == 'camera') {
390 | joinwithcamera();
391 | }
392 |
393 | if (first == 'join-with' && second == 'screen') {
394 | joinwithscreen();
395 | }
396 | };
397 |
398 | function preview() {
399 | connection.send({
400 | signalingmessage: true,
401 | shareoneway: true,
402 | hasmicrophone: !!data.hasmicrophone,
403 | hascamera: !!data.hascamera,
404 | hasscreen: !!data.hasscreen
405 | });
406 | }
407 |
408 | function joinwithmicrophone() {
409 | conversation.peer.peers[targetuser].addStream({
410 | audio: true,
411 | oneway: true
412 | });
413 | }
414 |
415 | function joinwithcamera() {
416 | conversation.peer.peers[targetuser].addStream({
417 | audio: true,
418 | video: true,
419 | oneway: true
420 | });
421 | }
422 |
423 | function joinwithscreen() {
424 | conversation.peer.peers[targetuser].addStream({
425 | screen: true,
426 | oneway: true
427 | });
428 | }
429 |
430 | conversation.emit('--media-enabled', event.data);
431 | }
432 |
433 | if (data.shareoneway) {
434 | if (data.hasmicrophone) {
435 | if (!user.localstreams.microphone) throw 'You have not allowed microphone.';
436 | connection.renegotiate();
437 | }
438 |
439 | if (data.hascamera) {
440 | if (!user.localstreams.camera) throw 'You have not allowed camera.';
441 | connection.renegotiate();
442 | }
443 |
444 | if (data.hasscreen) {
445 | if (!user.localstreams.screen) throw 'You have not allowed screen.';
446 | connection.renegotiate();
447 | }
448 | }
449 |
450 | if (data.addedfile) {
451 | var filesinfo = data.filesinfo;
452 | if (filesinfo.size && filesinfo.type) {
453 | filesinfo = eventHanlders(filesinfo);
454 | conversation.emit('--add-file', filesinfo);
455 | } else {
456 | for (var file in filesinfo) {
457 | filesinfo[file] = eventHanlders(filesinfo[file]);
458 | conversation.emit('--add-file', filesinfo[file]);
459 | }
460 | }
461 |
462 | function eventHanlders(file) {
463 | file.sender = targetuser;
464 |
465 | file.download = function() {
466 | conversation.peer.send({
467 | signalingmessage: true,
468 | download: true,
469 | sender: user.username,
470 | file: {
471 | size: file.size,
472 | type: file.type,
473 | name: file.name,
474 | lastModifiedDate: file.lastModifiedDate,
475 | uuid: file.uuid
476 | }
477 | });
478 | };
479 |
480 | file.cancel = function() {
481 | conversation.peer.send({
482 | signalingmessage: true,
483 | download: false,
484 | sender: user.username,
485 | file: {
486 | size: file.size,
487 | type: file.type,
488 | name: file.name,
489 | lastModifiedDate: file.lastModifiedDate,
490 | uuid: file.uuid
491 | }
492 | });
493 | };
494 |
495 | return file;
496 | }
497 | }
498 |
499 | if (data.file) {
500 | if (data.download) {
501 | var file = conversation.attachie.files[data.file.name];
502 | if ((file.item && file.length && file[0] && file[0].lastModifiedDate) || file.forEach) {
503 | Array.prototype.slice.call(file).forEach(function(f) {
504 | conversation.peer.send(f);
505 | });
506 | } else conversation.peer.send(file);
507 | } else {
508 | var file = conversation.attachie.files[data.file.name];
509 | conversation.emit('--file-cancelled', file);
510 | }
511 | }
512 | } else {
513 | event.username = conversation.targetuser;
514 | conversation.emit('--message', event);
515 | }
516 | };
517 |
518 | // overriding "openSignalingChannel" method
519 | connection.openSignalingChannel = function(config) {
520 | var channel = config.channel || this.channel;
521 |
522 | user.onmessagecallbacks[channel] = config.onmessage;
523 |
524 | if (config.onopen) setTimeout(config.onopen, 1000);
525 |
526 | // directly returning socket object using "return" statement
527 | return {
528 | send: function(message) {
529 | user.emit('signal', {
530 | message: message,
531 | isrtcmulticonnectioninnermessage: true,
532 | channel: channel
533 | });
534 | },
535 | channel: channel
536 | };
537 | };
538 |
539 | // todo: renegotiation doesn't work if trickleIce=false
540 | // need to fix it.
541 | // connection.trickleIce = false;
542 |
543 | connection.sdpConstraints.mandatory = {
544 | OfferToReceiveAudio: true,
545 | OfferToReceiveVideo: true
546 | };
547 |
548 | connection.autoSaveToDisk = false;
549 |
550 | connection.onFileEnd = function(file) {
551 | if (conversation.attachie.files[file.name]) {
552 | conversation.emit('--file-sent', file);
553 | return;
554 | }
555 |
556 | file.savetodisk = function(filename) {
557 | connection.saveToDisk(file, filename || file.name || file.type);
558 | };
559 |
560 | file.sender = file.userid;
561 |
562 | conversation.emit('--file-downloaded', file);
563 | };
564 |
565 | connection.onFileProgress = function(chunk) {
566 | var progress = {
567 | percentage: Math.round((chunk.currentPosition / chunk.maxChunks) * 100),
568 | uuid: chunk.uuid,
569 | file: chunk,
570 | sender: chunk.userid
571 | };
572 |
573 | if (progress.percentage > 100) progress.percentage = 100;
574 |
575 | conversation.emit('--file-progress', progress);
576 | };
577 |
578 | connection.onFileStart = function() {};
579 |
580 | connection.onstreamid = function(event) {
581 | conversation.emit('--stream-clue', event);
582 | };
583 |
584 | connection.onstream = function(event) {
585 | conversation.emit('--stream', event);
586 | };
587 |
588 | connection.onstreamended = function(event) {
589 | conversation.emit('--stream-ended', event);
590 | };
591 |
592 | if (args.isInitiator) {
593 | connection.open({
594 | dontTransmit: true
595 | });
596 | }
597 |
598 | user.peers[args.targetuser] = connection;
599 |
600 | user.peers.length++;
601 | connection.onleave = function() {
602 | user.peers.length--;
603 | };
604 |
605 | user.emit('signal', {
606 | joinRoom: true,
607 | responsefor: args.targetuser,
608 | sessionDescription: connection.sessionDescription
609 | });
610 |
611 | conversation.peer = connection;
612 | }
613 |
614 | function addfile(file) {
615 | var filesinfo = {};
616 |
617 | // seems array of files
618 | if ((file.item && file.length && file[0] && file[0].lastModifiedDate) || file.forEach) {
619 | Array.prototype.slice.call(file).forEach(function(f) {
620 | if (!f.uuid) f.uuid = user.randomstring();
621 | filesinfo[file.name] = {
622 | size: f.size,
623 | type: f.type,
624 | name: f.name,
625 | lastModifiedDate: f.lastModifiedDate,
626 | uuid: f.uuid
627 | };
628 | conversation.attachie.files[f.name] = f;
629 | });
630 | } else {
631 | if (!file.uuid) file.uuid = user.randomstring();
632 | filesinfo[file.name] = {
633 | size: file.size,
634 | type: file.type,
635 | name: file.name,
636 | lastModifiedDate: file.lastModifiedDate,
637 | uuid: file.uuid
638 | };
639 |
640 | conversation.attachie.files[file.name] = file;
641 | }
642 |
643 | conversation.peer.send({
644 | addedfile: true,
645 | filesinfo: filesinfo,
646 | signalingmessage: true,
647 | sender: user.username
648 | });
649 | }
650 |
651 | // custom events
652 |
653 | conversation.events = {};
654 | conversation.on = function(event, callback) {
655 | conversation.events[event] = callback;
656 | };
657 |
658 | conversation.emit = function() {
659 | if (!arguments[0]) throw 'At least one argument is mandatory.';
660 |
661 | var internalevent = arguments[0].indexOf('--') == 0;
662 | if (!internalevent) {
663 | if (arguments[0] == 'message') {
664 | sendmessage(arguments[1]);
665 | }
666 |
667 | if (arguments[0] == 'add-file' && arguments[1]) {
668 | addfile(arguments[1]);
669 | }
670 |
671 | if (arguments[0] == 'enable') {
672 | if (arguments[1] == 'microphone') {
673 | enablemicrophone();
674 | }
675 |
676 | if (arguments[1] == 'camera') {
677 | enablecamera();
678 | }
679 |
680 | if (arguments[1] == 'screen') {
681 | enablescreen();
682 | }
683 | }
684 |
685 | if (arguments[0] == 'end') {
686 | conversation.peer.close();
687 | user.emit('--ended');
688 | }
689 |
690 | return;
691 | }
692 |
693 | if (internalevent) {
694 | arguments[0] = arguments[0].replace('--', '');
695 | }
696 |
697 | if (!conversation.events[arguments[0]]) {
698 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
699 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
700 | console.warn(warning);
701 |
702 | } else conversation.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
703 | };
704 |
705 | conversation.on('add-file', function(file) {
706 | file.download();
707 | });
708 |
709 | conversation.on('file-downloaded', function(file) {
710 | file.savetodisk();
711 | });
712 |
713 | conversation.on('media-enabled', function(media) {
714 | if (media.hasmicrophone) {
715 | media.emit('join-with', 'microphone');
716 | }
717 |
718 | if (media.hascamera) {
719 | media.emit('join-with', 'camera');
720 | }
721 |
722 | if (media.hasscreen) {
723 | media.emit('join-with', 'nothing');
724 | }
725 | });
726 |
727 | conversation.on('stream-clue', function(stream) {
728 | user.emit('--log', 'stream-clue: ' + stream.streamid);
729 | });
730 |
731 | conversation.on('stream', function(stream) {
732 | user.emit('--log', 'stream: ' + stream.streamid);
733 |
734 | conversation.peer.body.insertBefore(stream.mediaElement, conversation.peer.body.firstChild);
735 | });
736 |
737 | conversation.on('stream-ended', function(stream) {
738 | user.emit('--log', 'stream-ended: ' + stream.streamid);
739 |
740 | if (stream.mediaElement && stream.mediaElement.parentNode) {
741 | stream.mediaElement.parentNode.removeChild(stream.mediaElement);
742 | }
743 | });
744 |
745 | user.conversations[targetuser] = conversation;
746 | }
747 | })();
748 |
--------------------------------------------------------------------------------
/AndroidRTC/scripts/conversation.js:
--------------------------------------------------------------------------------
1 | // Last time updated at Sep 15, 2014, 08:32:23
2 |
3 | // Latest file can be found here: https://cdn.webrtc-experiment.com/conversation.js
4 |
5 | // Muaz Khan - www.MuazKhan.com
6 | // MIT License - www.WebRTC-Experiment.com/licence
7 | // _______________
8 | // Conversation.js
9 |
10 | /*
11 | -. Signaler object added. See below for how to use it.
12 |
13 | var signaler = new Signaler();
14 | signaler.on('message', function(message) {
15 | socket.send(message);
16 | });
17 |
18 | socket.on('message', function(message) {
19 | signaler.emit('message', message);
20 | });
21 |
22 | var user = new User();
23 |
24 | signaler.connect(user);
25 | */
26 |
27 | (function() {
28 | window.Signaler = function() {
29 | var signaler = this;
30 | var users = [];
31 | signaler.connect = function(user) {
32 | user.signaler = signaler;
33 | user.emit('--signaler-connected');
34 | users.push(user);
35 | };
36 |
37 | signaler.send = function(data) {
38 | signaler.emit('--message', data);
39 | };
40 |
41 | signaler.events = {};
42 | signaler.on = function(event, callback) {
43 | signaler.events[event] = callback;
44 | };
45 |
46 | signaler.emit = function() {
47 | if (!arguments[0]) throw 'At least one argument is mandatory.';
48 |
49 | var internalevent = arguments[0].indexOf('--') == 0;
50 | if (!internalevent) {
51 | if (arguments[0] == 'message') {
52 | var message = arguments[1];
53 | users.forEach(function(user) {
54 | user.onsignalingmessage(message);
55 | });
56 | }
57 |
58 | return;
59 | }
60 |
61 | if (internalevent) {
62 | arguments[0] = arguments[0].replace('--', '');
63 | }
64 |
65 | if (!signaler.events[arguments[0]]) {
66 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
67 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
68 | console.warn(warning);
69 |
70 | } else signaler.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
71 | };
72 | };
73 |
74 | window.User = function(_defaults) {
75 | var user = this;
76 |
77 | user.randomstring = function() {
78 | // suggested by @rvulpescu from #154
79 | if (window.crypto && crypto.getRandomValues && navigator.userAgent.indexOf('Safari') == -1) {
80 | var a = window.crypto.getRandomValues(new Uint32Array(3)),
81 | token = '';
82 | for (var i = 0, l = a.length; i < l; i++) {
83 | token += a[i].toString(36);
84 | }
85 | return token;
86 | } else {
87 | return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
88 | }
89 | };
90 |
91 | user.defaults = {};
92 | user.username = user.randomstring();
93 | user.status = 'online';
94 |
95 | user.openconversationwith = function(targetuser) {
96 | var conversation = new Conversation(user, targetuser);
97 |
98 | // check who is online
99 | user.emit('signal', {
100 | whoisonline: true,
101 | responsefor: targetuser
102 | });
103 | };
104 |
105 | user.onmessagecallbacks = {};
106 | user.onsignalingmessage = function(message) {
107 | if (message.isrtcmulticonnectioninnermessage && user.onmessagecallbacks[message.channel]) {
108 | return user.onmessagecallbacks[message.channel](message.message);
109 | }
110 |
111 | if (message.searchuser) {
112 | if (user.status === 'online') {
113 | user.emit('signal', {
114 | useronline: true,
115 | responsefor: message.sender
116 | });
117 | }
118 | return;
119 | }
120 |
121 | if (message.useronline && message.responsefor == user.username) {
122 | user.emit('--search', {
123 | username: message.sender
124 | });
125 | return;
126 | }
127 |
128 | if (message.whoisonline && message.responsefor == user.username && user.status == 'online') {
129 | user.emit('--friend-request', {
130 | accept: function() {
131 | if (!user.peers[message.sender]) {
132 | var randomchannel = user.randomstring();
133 | user.emit('signal', {
134 | iamonline: true,
135 | responsefor: message.sender,
136 | randomchannel: randomchannel
137 | });
138 |
139 | var conversation = new Conversation(user, message.sender);
140 |
141 | conversation.addnewrtcmulticonnectionpeer({
142 | targetuser: message.sender,
143 | channel: randomchannel
144 | });
145 | }
146 |
147 | user.emit('signal', {
148 | requestaccepted: true,
149 | sender: user.username,
150 | responsefor: message.sender
151 | });
152 | },
153 | reject: function() {
154 | user.emit('signal', {
155 | requestrejected: true,
156 | sender: user.username,
157 | responsefor: message.sender
158 | });
159 | },
160 | sender: message.sender
161 | });
162 | }
163 |
164 | if (message.requestaccepted && message.responsefor == user.username) {
165 | user.emit('--request-status', {
166 | status: 'accepted',
167 | sender: message.sender
168 | });
169 | }
170 |
171 | if (message.requestrejected && message.responsefor == user.username) {
172 | user.emit('--request-status', {
173 | status: 'accepted',
174 | sender: message.sender
175 | });
176 | }
177 |
178 | if (message.iamonline && message.responsefor == user.username) {
179 | if (!user.peers[message.sender]) {
180 | user.conversations[message.sender].addnewrtcmulticonnectionpeer({
181 | targetuser: message.sender,
182 | channel: message.randomchannel,
183 | isInitiator: true
184 | });
185 | }
186 | }
187 |
188 | if (message.sessionDescription && message.responsefor == user.username) {
189 | user.peers[message.sender].join(message.sessionDescription);
190 | }
191 | }
192 |
193 | // reference to all RTCMultiConnection peers
194 | user.peers = {
195 | emit: function(eventName, value) {
196 | for (var conversation in user.conversations) {
197 | user.conversations[conversation].emit(eventName, value);
198 | }
199 | },
200 | length: 0
201 | };
202 |
203 | // reference to all Conversation objects
204 | user.conversations = {};
205 |
206 | // reference to all local media streams
207 | user.localstreams = {};
208 |
209 | user.events = {};
210 | user.on = function(event, callback) {
211 | user.events[event] = callback;
212 | };
213 |
214 | user.emit = function() {
215 | if (!arguments[0]) throw 'At least one argument is mandatory.';
216 |
217 | var internalevent = arguments[0].indexOf('--') == 0;
218 | if (!internalevent) {
219 | if (arguments[0] == 'search') {
220 | user.emit('signal', {
221 | searchuser: arguments[1]
222 | });
223 | }
224 |
225 | if (arguments[0] == 'signal') {
226 | var data = arguments[1];
227 | data.sender = user.username;
228 | user.signaler.emit('--message', data);
229 | }
230 |
231 | return;
232 | }
233 |
234 | if (internalevent) {
235 | arguments[0] = arguments[0].replace('--', '');
236 | if (arguments[0] == 'log' && user.defaults.log == false) return;
237 | }
238 |
239 | if (!user.events[arguments[0]]) {
240 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
241 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
242 | console.warn(warning);
243 |
244 | } else user.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
245 | };
246 |
247 | // defaults
248 | user.on('request-status', function(request) {
249 | user.emit('--log', request.sender + ' ' + request.status + ' your request.');
250 | });
251 |
252 | user.on('friend-request', function(request) {
253 | request.accept();
254 | });
255 |
256 | user.on('signaler-connected', function() {
257 | user.emit('--log', 'Signaling medium is ready for pub/sub.');
258 | });
259 |
260 | user.on('log', function(message) {
261 | console.log(message);
262 | });
263 |
264 | for (var data in _defaults || {}) {
265 | user.defaults[data] = _defaults[data];
266 | }
267 |
268 | if (_defaults) {
269 | user.emit('signaler', 'start');
270 | }
271 | };
272 |
273 | function Conversation(user, targetuser) {
274 | var conversation = this;
275 |
276 | function sendmessage(data) {
277 | conversation.peer.send(data);
278 | }
279 |
280 | function enablemicrophone() {
281 | if (user.localstreams.microphone) return;
282 |
283 | conversation.peer.captureUserMedia(function(stream) {
284 | user.localstreams.microphone = stream;
285 |
286 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
287 |
288 | conversation.peer.send({
289 | signalingmessage: true,
290 | hasmicrophone: true,
291 | streamavailable: true,
292 | sender: user.username,
293 | type: 'audio'
294 | });
295 | }, {
296 | audio: true
297 | });
298 | }
299 |
300 | function enablecamera() {
301 | if (user.localstreams.camera) return;
302 |
303 | conversation.peer.captureUserMedia(function(stream) {
304 | user.localstreams.camera = stream;
305 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
306 |
307 | conversation.peer.send({
308 | signalingmessage: true,
309 | hascamera: true,
310 | streamavailable: true,
311 | sender: user.username,
312 | type: 'video'
313 | });
314 | }, {
315 | audio: true,
316 | video: true
317 | });
318 | }
319 |
320 | function enablescreen() {
321 | if (user.localstreams.screen) return;
322 |
323 | conversation.peer.captureUserMedia(function(stream) {
324 | user.localstreams.screen = stream;
325 |
326 | conversation.peer.peers[targetuser].peer.connection.addStream(stream);
327 |
328 | conversation.peer.send({
329 | signalingmessage: true,
330 | hasscreen: true,
331 | streamavailable: true,
332 | sender: user.username,
333 | type: 'screen'
334 | });
335 | }, {
336 | screen: true
337 | });
338 | };
339 |
340 | conversation.attachie = {
341 | files: {},
342 | messages: {}
343 | };
344 |
345 | conversation.addnewrtcmulticonnectionpeer = function(args) {
346 | var connection = new RTCMultiConnection(args.channel);
347 |
348 | for (var d in user.defaults) {
349 | if (typeof connection[d] !== 'undefined' /* && typeof connection[d] !== 'function' */ ) {
350 | connection[d] = user.defaults[d];
351 | }
352 | }
353 |
354 | // workaround for RTCMultiConnection-v1.8 or older
355 | if (user.defaults.log == false) {
356 | connection.skipLogs();
357 | }
358 |
359 | connection.userid = user.username;
360 |
361 | // v1.9 and onwards supports "onlog" event.
362 | connection.onlog = function(message) {
363 | user.emit('--log', JSON.stringify(message, null, '\t'));
364 | };
365 |
366 | connection.session = {
367 | data: true
368 | };
369 |
370 | connection.onopen = function() {
371 | conversation.targetuser = args.targetuser;
372 | user.emit('--conversation-opened', conversation);
373 | };
374 |
375 | connection.onmessage = function(event) {
376 | if (event.data.signalingmessage) {
377 | var data = event.data;
378 |
379 | if (data.streamavailable) {
380 | data.emit = function(first, second) {
381 | if (first == 'join-with' && second == 'nothing') {
382 | preview();
383 | }
384 |
385 | if (first == 'join-with' && second == 'microphone') {
386 | joinwithmicrophone();
387 | }
388 |
389 | if (first == 'join-with' && second == 'camera') {
390 | joinwithcamera();
391 | }
392 |
393 | if (first == 'join-with' && second == 'screen') {
394 | joinwithscreen();
395 | }
396 | };
397 |
398 | function preview() {
399 | connection.send({
400 | signalingmessage: true,
401 | shareoneway: true,
402 | hasmicrophone: !!data.hasmicrophone,
403 | hascamera: !!data.hascamera,
404 | hasscreen: !!data.hasscreen
405 | });
406 | }
407 |
408 | function joinwithmicrophone() {
409 | conversation.peer.peers[targetuser].addStream({
410 | audio: true,
411 | oneway: true
412 | });
413 | }
414 |
415 | function joinwithcamera() {
416 | conversation.peer.peers[targetuser].addStream({
417 | audio: true,
418 | video: true,
419 | oneway: true
420 | });
421 | }
422 |
423 | function joinwithscreen() {
424 | conversation.peer.peers[targetuser].addStream({
425 | screen: true,
426 | oneway: true
427 | });
428 | }
429 |
430 | conversation.emit('--media-enabled', event.data);
431 | }
432 |
433 | if (data.shareoneway) {
434 | if (data.hasmicrophone) {
435 | if (!user.localstreams.microphone) throw 'You have not allowed microphone.';
436 | connection.renegotiate();
437 | }
438 |
439 | if (data.hascamera) {
440 | if (!user.localstreams.camera) throw 'You have not allowed camera.';
441 | connection.renegotiate();
442 | }
443 |
444 | if (data.hasscreen) {
445 | if (!user.localstreams.screen) throw 'You have not allowed screen.';
446 | connection.renegotiate();
447 | }
448 | }
449 |
450 | if (data.addedfile) {
451 | var filesinfo = data.filesinfo;
452 | if (filesinfo.size && filesinfo.type) {
453 | filesinfo = eventHanlders(filesinfo);
454 | conversation.emit('--add-file', filesinfo);
455 | } else {
456 | for (var file in filesinfo) {
457 | filesinfo[file] = eventHanlders(filesinfo[file]);
458 | conversation.emit('--add-file', filesinfo[file]);
459 | }
460 | }
461 |
462 | function eventHanlders(file) {
463 | file.sender = targetuser;
464 |
465 | file.download = function() {
466 | conversation.peer.send({
467 | signalingmessage: true,
468 | download: true,
469 | sender: user.username,
470 | file: {
471 | size: file.size,
472 | type: file.type,
473 | name: file.name,
474 | lastModifiedDate: file.lastModifiedDate,
475 | uuid: file.uuid
476 | }
477 | });
478 | };
479 |
480 | file.cancel = function() {
481 | conversation.peer.send({
482 | signalingmessage: true,
483 | download: false,
484 | sender: user.username,
485 | file: {
486 | size: file.size,
487 | type: file.type,
488 | name: file.name,
489 | lastModifiedDate: file.lastModifiedDate,
490 | uuid: file.uuid
491 | }
492 | });
493 | };
494 |
495 | return file;
496 | }
497 | }
498 |
499 | if (data.file) {
500 | if (data.download) {
501 | var file = conversation.attachie.files[data.file.name];
502 | if ((file.item && file.length && file[0] && file[0].lastModifiedDate) || file.forEach) {
503 | Array.prototype.slice.call(file).forEach(function(f) {
504 | conversation.peer.send(f);
505 | });
506 | } else conversation.peer.send(file);
507 | } else {
508 | var file = conversation.attachie.files[data.file.name];
509 | conversation.emit('--file-cancelled', file);
510 | }
511 | }
512 | } else {
513 | event.username = conversation.targetuser;
514 | conversation.emit('--message', event);
515 | }
516 | };
517 |
518 | // overriding "openSignalingChannel" method
519 | connection.openSignalingChannel = function(config) {
520 | var channel = config.channel || this.channel;
521 |
522 | user.onmessagecallbacks[channel] = config.onmessage;
523 |
524 | if (config.onopen) setTimeout(config.onopen, 1000);
525 |
526 | // directly returning socket object using "return" statement
527 | return {
528 | send: function(message) {
529 | user.emit('signal', {
530 | message: message,
531 | isrtcmulticonnectioninnermessage: true,
532 | channel: channel
533 | });
534 | },
535 | channel: channel
536 | };
537 | };
538 |
539 | // todo: renegotiation doesn't work if trickleIce=false
540 | // need to fix it.
541 | // connection.trickleIce = false;
542 |
543 | connection.sdpConstraints.mandatory = {
544 | OfferToReceiveAudio: true,
545 | OfferToReceiveVideo: true
546 | };
547 |
548 | connection.autoSaveToDisk = false;
549 |
550 | connection.onFileEnd = function(file) {
551 | if (conversation.attachie.files[file.name]) {
552 | conversation.emit('--file-sent', file);
553 | return;
554 | }
555 |
556 | file.savetodisk = function(filename) {
557 | connection.saveToDisk(file, filename || file.name || file.type);
558 | };
559 |
560 | file.sender = file.userid;
561 |
562 | conversation.emit('--file-downloaded', file);
563 | };
564 |
565 | connection.onFileProgress = function(chunk) {
566 | var progress = {
567 | percentage: Math.round((chunk.currentPosition / chunk.maxChunks) * 100),
568 | uuid: chunk.uuid,
569 | file: chunk,
570 | sender: chunk.userid
571 | };
572 |
573 | if (progress.percentage > 100) progress.percentage = 100;
574 |
575 | conversation.emit('--file-progress', progress);
576 | };
577 |
578 | connection.onFileStart = function() {};
579 |
580 | connection.onstreamid = function(event) {
581 | conversation.emit('--stream-clue', event);
582 | };
583 |
584 | connection.onstream = function(event) {
585 | conversation.emit('--stream', event);
586 | };
587 |
588 | connection.onstreamended = function(event) {
589 | conversation.emit('--stream-ended', event);
590 | };
591 |
592 | if (args.isInitiator) {
593 | connection.open({
594 | dontTransmit: true
595 | });
596 | }
597 |
598 | user.peers[args.targetuser] = connection;
599 |
600 | user.peers.length++;
601 | connection.onleave = function() {
602 | user.peers.length--;
603 | };
604 |
605 | user.emit('signal', {
606 | joinRoom: true,
607 | responsefor: args.targetuser,
608 | sessionDescription: connection.sessionDescription
609 | });
610 |
611 | conversation.peer = connection;
612 | }
613 |
614 | function addfile(file) {
615 | var filesinfo = {};
616 |
617 | // seems array of files
618 | if ((file.item && file.length && file[0] && file[0].lastModifiedDate) || file.forEach) {
619 | Array.prototype.slice.call(file).forEach(function(f) {
620 | if (!f.uuid) f.uuid = user.randomstring();
621 | filesinfo[file.name] = {
622 | size: f.size,
623 | type: f.type,
624 | name: f.name,
625 | lastModifiedDate: f.lastModifiedDate,
626 | uuid: f.uuid
627 | };
628 | conversation.attachie.files[f.name] = f;
629 | });
630 | } else {
631 | if (!file.uuid) file.uuid = user.randomstring();
632 | filesinfo[file.name] = {
633 | size: file.size,
634 | type: file.type,
635 | name: file.name,
636 | lastModifiedDate: file.lastModifiedDate,
637 | uuid: file.uuid
638 | };
639 |
640 | conversation.attachie.files[file.name] = file;
641 | }
642 |
643 | conversation.peer.send({
644 | addedfile: true,
645 | filesinfo: filesinfo,
646 | signalingmessage: true,
647 | sender: user.username
648 | });
649 | }
650 |
651 | // custom events
652 |
653 | conversation.events = {};
654 | conversation.on = function(event, callback) {
655 | conversation.events[event] = callback;
656 | };
657 |
658 | conversation.emit = function() {
659 | if (!arguments[0]) throw 'At least one argument is mandatory.';
660 |
661 | var internalevent = arguments[0].indexOf('--') == 0;
662 | if (!internalevent) {
663 | if (arguments[0] == 'message') {
664 | sendmessage(arguments[1]);
665 | }
666 |
667 | if (arguments[0] == 'add-file' && arguments[1]) {
668 | addfile(arguments[1]);
669 | }
670 |
671 | if (arguments[0] == 'enable') {
672 | if (arguments[1] == 'microphone') {
673 | enablemicrophone();
674 | }
675 |
676 | if (arguments[1] == 'camera') {
677 | enablecamera();
678 | }
679 |
680 | if (arguments[1] == 'screen') {
681 | enablescreen();
682 | }
683 | }
684 |
685 | if (arguments[0] == 'end') {
686 | conversation.peer.close();
687 | user.emit('--ended');
688 | }
689 |
690 | return;
691 | }
692 |
693 | if (internalevent) {
694 | arguments[0] = arguments[0].replace('--', '');
695 | }
696 |
697 | if (!conversation.events[arguments[0]]) {
698 | var warning = 'Event name "' + arguments[0] + '" doesn\'t exists.';
699 | if (arguments[1]) warning += ' Values: ' + JSON.stringify(arguments[1], null, '\t');
700 | console.warn(warning);
701 |
702 | } else conversation.events[arguments[0]](arguments[1], arguments[2], arguments[3], arguments[4]);
703 | };
704 |
705 | conversation.on('add-file', function(file) {
706 | file.download();
707 | });
708 |
709 | conversation.on('file-downloaded', function(file) {
710 | file.savetodisk();
711 | });
712 |
713 | conversation.on('media-enabled', function(media) {
714 | if (media.hasmicrophone) {
715 | media.emit('join-with', 'microphone');
716 | }
717 |
718 | if (media.hascamera) {
719 | media.emit('join-with', 'camera');
720 | }
721 |
722 | if (media.hasscreen) {
723 | media.emit('join-with', 'nothing');
724 | }
725 | });
726 |
727 | conversation.on('stream-clue', function(stream) {
728 | user.emit('--log', 'stream-clue: ' + stream.streamid);
729 | });
730 |
731 | conversation.on('stream', function(stream) {
732 | user.emit('--log', 'stream: ' + stream.streamid);
733 |
734 | conversation.peer.body.insertBefore(stream.mediaElement, conversation.peer.body.firstChild);
735 | });
736 |
737 | conversation.on('stream-ended', function(stream) {
738 | user.emit('--log', 'stream-ended: ' + stream.streamid);
739 |
740 | if (stream.mediaElement && stream.mediaElement.parentNode) {
741 | stream.mediaElement.parentNode.removeChild(stream.mediaElement);
742 | }
743 | });
744 |
745 | user.conversations[targetuser] = conversation;
746 | }
747 | })();
748 |
--------------------------------------------------------------------------------
/AndroidRTC/scripts/FileBufferReader.js:
--------------------------------------------------------------------------------
1 | // Last time updated at Sep 16, 2014, 08:32:23
2 |
3 | // Latest file can be found here: https://cdn.webrtc-experiment.com/FileBufferReader.js
4 |
5 | // Muaz Khan - www.MuazKhan.com
6 | // MIT License - www.WebRTC-Experiment.com/licence
7 | // Source Code - https://github.com/muaz-khan/FileBufferReader
8 | // Demo - https://www.WebRTC-Experiment.com/FileBufferReader/
9 |
10 | // ___________________
11 | // FileBufferReader.js
12 |
13 | // FileBufferReader.js uses binarize.js to convert Objects into array-buffers and vice versa.
14 | // FileBufferReader.js is MIT licensed: www.WebRTC-Experiment.com/licence
15 | // binarize.js is written by @agektmr: https://github.com/agektmr/binarize.js.
16 |
17 | /* issues/features need to be fixed & implemented:
18 | -. "onEnd" for sender now having "url" property as well; same as file receiver.
19 | -. "extra" must not be an empty object i.e. {} -because "binarize" fails to parse empty objects.
20 | -. "extra" must not have "date" types; -because "binarize" fails to parse date-types.
21 | */
22 |
23 | (function() {
24 | window.FileBufferReader = function() {
25 | var fileBufferReader = this;
26 | fileBufferReader.chunks = {};
27 |
28 | fileBufferReader.readAsArrayBuffer = function(file, callback, extra) {
29 | extra = extra || {};
30 | extra.chunkSize = extra.chunkSize || 12 * 1000; // Firefox limit is 16k
31 |
32 | File.Read(file, function(args) {
33 | file.extra = extra || {};
34 | file.url = URL.createObjectURL(file);
35 | args.file = file; // passed over "onEnd"
36 | fileBufferReader.chunks[args.uuid] = args;
37 | callback(args.uuid);
38 | }, extra);
39 | };
40 |
41 | fileBufferReader.getNextChunk = function(uuid, callback) {
42 | var chunks = fileBufferReader.chunks[uuid];
43 | if (!chunks) return;
44 |
45 | var currentChunk = chunks.listOfChunks[chunks.currentPosition];
46 | var isLastChunk = currentChunk && currentChunk.end;
47 |
48 | FileConverter.ConvertToArrayBuffer(currentChunk, function(buffer) {
49 | if (chunks.currentPosition == 0) {
50 | fileBufferReader.onBegin(chunks.file);
51 | }
52 |
53 | if (isLastChunk) {
54 | fileBufferReader.onEnd(chunks.file);
55 | }
56 |
57 | callback(buffer, isLastChunk, currentChunk.extra);
58 | fileBufferReader.onProgress({
59 | currentPosition: chunks.currentPosition,
60 | maxChunks: chunks.maxChunks,
61 | uuid: chunks.uuid,
62 | extra: currentChunk.extra
63 | });
64 | fileBufferReader.chunks[uuid].currentPosition++;
65 | });
66 | };
67 |
68 | fileBufferReader.onBegin = fileBufferReader.onProgress = fileBufferReader.onEnd = function() {};
69 |
70 | var receiver = new File.Receiver(fileBufferReader);
71 |
72 | fileBufferReader.addChunk = function(chunk, callback) {
73 | receiver.receive(chunk, function(uuid) {
74 | FileConverter.ConvertToArrayBuffer({
75 | readyForNextChunk: true,
76 | uuid: uuid
77 | }, callback);
78 | });
79 | };
80 |
81 | fileBufferReader.convertToObject = FileConverter.ConvertToObject;
82 | };
83 |
84 | window.FileSelector = function() {
85 | var selector = this;
86 |
87 | selector.selectSingleFile = selectFile;
88 | selector.selectMultipleFiles = function(callback) {
89 | selectFile(callback, true);
90 | };
91 |
92 | function selectFile(callback, multiple) {
93 | var file = document.createElement('input');
94 | file.type = 'file';
95 |
96 | if (multiple) {
97 | file.multiple = true;
98 | }
99 |
100 | file.onchange = function() {
101 | callback(multiple ? file.files : file.files[0]);
102 | };
103 | fireClickEvent(file);
104 | }
105 |
106 | function fireClickEvent(element) {
107 | var evt = new window.MouseEvent('click', {
108 | view: window,
109 | bubbles: true,
110 | cancelable: true
111 | });
112 |
113 | element.dispatchEvent(evt);
114 | }
115 | };
116 |
117 | var File = {
118 | Read: function(file, callback, extra) {
119 | var numOfChunksInSlice;
120 | var currentPosition = 1;
121 | var hasEntireFile;
122 | var listOfChunks = {};
123 |
124 | var chunkSize = extra.chunkSize || 60 * 1000; // 64k max sctp limit (AFAIK!)
125 |
126 | var sliceId = 0;
127 | var cacheSize = chunkSize;
128 |
129 | var chunksPerSlice = Math.floor(Math.min(100000000, cacheSize) / chunkSize);
130 | var sliceSize = chunksPerSlice * chunkSize;
131 | var maxChunks = Math.ceil(file.size / chunkSize);
132 |
133 | // uuid is used to uniquely identify sending instance
134 | var uuid = file.uuid || (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '-');
135 |
136 | listOfChunks[0] = {
137 | uuid: uuid,
138 | maxChunks: maxChunks,
139 | size: file.size,
140 | name: file.name,
141 | lastModifiedDate: file.lastModifiedDate + '',
142 | type: file.type,
143 | start: true,
144 | extra: extra
145 | };
146 |
147 | file.maxChunks = maxChunks;
148 | file.uuid = uuid;
149 |
150 | var blob, reader = new FileReader();
151 | reader.onloadend = function(evt) {
152 | if (evt.target.readyState == FileReader.DONE) {
153 | addChunks(file.name, evt.target.result, function() {
154 | sliceId++;
155 | if ((sliceId + 1) * sliceSize < file.size) {
156 | blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize);
157 | reader.readAsArrayBuffer(blob);
158 | } else if (sliceId * sliceSize < file.size) {
159 | blob = file.slice(sliceId * sliceSize, file.size);
160 | reader.readAsArrayBuffer(blob);
161 | } else {
162 | listOfChunks[currentPosition] = {
163 | uuid: uuid,
164 | maxChunks: maxChunks,
165 | size: file.size,
166 | name: file.name,
167 | lastModifiedDate: file.lastModifiedDate + '',
168 | type: file.type,
169 | end: true,
170 | extra: extra
171 | };
172 | callback({
173 | currentPosition: 0,
174 | listOfChunks: listOfChunks,
175 | maxChunks: maxChunks + 1,
176 | uuid: uuid,
177 | extra: extra
178 | });
179 | }
180 | });
181 | }
182 | };
183 |
184 | blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize);
185 | reader.readAsArrayBuffer(blob);
186 |
187 | function addChunks(fileName, binarySlice, callback) {
188 | numOfChunksInSlice = Math.ceil(binarySlice.byteLength / chunkSize);
189 | for (var i = 0; i < numOfChunksInSlice; i++) {
190 | var start = i * chunkSize;
191 | listOfChunks[currentPosition] = {
192 | uuid: uuid,
193 | value: binarySlice.slice(start, Math.min(start + chunkSize, binarySlice.byteLength)),
194 | currentPosition: currentPosition,
195 | maxChunks: maxChunks,
196 | extra: extra
197 | };
198 |
199 | currentPosition++;
200 | }
201 |
202 | if (currentPosition == maxChunks) {
203 | hasEntireFile = true;
204 | }
205 |
206 | callback();
207 | }
208 | },
209 |
210 | Receiver: function(config) {
211 | var packets = {};
212 |
213 | function receive(chunk, callback) {
214 | if (!chunk.uuid) {
215 | FileConverter.ConvertToObject(chunk, function(object) {
216 | receive(object);
217 | });
218 | return;
219 | }
220 |
221 | if (chunk.start && !packets[chunk.uuid]) {
222 | packets[chunk.uuid] = [];
223 | if (config.onBegin) config.onBegin(chunk);
224 | }
225 |
226 | if (!chunk.end && chunk.value) {
227 | packets[chunk.uuid].push(chunk.value);
228 | }
229 |
230 | if (chunk.end) {
231 | var _packets = packets[chunk.uuid];
232 | var finalArray = [],
233 | length = _packets.length;
234 |
235 | for (var i = 0; i < length; i++) {
236 | if (!!_packets[i]) {
237 | finalArray.push(_packets[i]);
238 | }
239 | }
240 |
241 | var blob = new Blob(finalArray, {
242 | type: chunk.type
243 | });
244 | blob = merge(blob, chunk);
245 | blob.url = URL.createObjectURL(blob);
246 | blob.uuid = chunk.uuid;
247 |
248 | if (!blob.size) console.error('Something went wrong. Blob Size is 0.');
249 |
250 | if (config.onEnd) config.onEnd(blob);
251 | }
252 |
253 | if (chunk.value && config.onProgress) config.onProgress(chunk);
254 |
255 | if (!chunk.end) callback(chunk.uuid);
256 | }
257 |
258 | return {
259 | receive: receive
260 | };
261 | },
262 | SaveToDisk: function(fileUrl, fileName) {
263 | var hyperlink = document.createElement('a');
264 | hyperlink.href = fileUrl;
265 | hyperlink.target = '_blank';
266 | hyperlink.download = fileName || fileUrl;
267 |
268 | var mouseEvent = new MouseEvent('click', {
269 | view: window,
270 | bubbles: true,
271 | cancelable: true
272 | });
273 |
274 | hyperlink.dispatchEvent(mouseEvent);
275 | (window.URL || window.webkitURL).revokeObjectURL(hyperlink.href);
276 | }
277 | };
278 |
279 | // ________________
280 | // FileConverter.js
281 | var FileConverter = {
282 | ConvertToArrayBuffer: function(object, callback) {
283 | binarize.pack(object, callback);
284 | },
285 | ConvertToObject: function(buffer, callback) {
286 | binarize.unpack(buffer, callback);
287 | }
288 | };
289 |
290 | function merge(mergein, mergeto) {
291 | if (!mergein) mergein = {};
292 | if (!mergeto) return mergein;
293 |
294 | for (var item in mergeto) {
295 | mergein[item] = mergeto[item];
296 | }
297 | return mergein;
298 | }
299 |
300 | /*
301 | Copyright 2013 Eiji Kitamura
302 |
303 | Licensed under the Apache License, Version 2.0 (the "License");
304 | you may not use this file except in compliance with the License.
305 | You may obtain a copy of the License at
306 |
307 | http://www.apache.org/licenses/LICENSE-2.0
308 |
309 | Unless required by applicable law or agreed to in writing, software
310 | distributed under the License is distributed on an "AS IS" BASIS,
311 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
312 | See the License for the specific language governing permissions and
313 | limitations under the License.
314 |
315 | Author: Eiji Kitamura (agektmr@gmail.com)
316 | */
317 | (function(root) {
318 | var debug = false;
319 |
320 | var BIG_ENDIAN = false,
321 | LITTLE_ENDIAN = true,
322 | TYPE_LENGTH = Uint8Array.BYTES_PER_ELEMENT,
323 | LENGTH_LENGTH = Uint16Array.BYTES_PER_ELEMENT,
324 | BYTES_LENGTH = Uint32Array.BYTES_PER_ELEMENT;
325 |
326 | var Types = {
327 | NULL: 0,
328 | UNDEFINED: 1,
329 | STRING: 2,
330 | NUMBER: 3,
331 | BOOLEAN: 4,
332 | ARRAY: 5,
333 | OBJECT: 6,
334 | INT8ARRAY: 7,
335 | INT16ARRAY: 8,
336 | INT32ARRAY: 9,
337 | UINT8ARRAY: 10,
338 | UINT16ARRAY: 11,
339 | UINT32ARRAY: 12,
340 | FLOAT32ARRAY: 13,
341 | FLOAT64ARRAY: 14,
342 | ARRAYBUFFER: 15,
343 | BLOB: 16,
344 | FILE: 16,
345 | BUFFER: 17 // Special type for node.js
346 | };
347 |
348 | if (debug) {
349 | var TypeNames = [
350 | 'NULL',
351 | 'UNDEFINED',
352 | 'STRING',
353 | 'NUMBER',
354 | 'BOOLEAN',
355 | 'ARRAY',
356 | 'OBJECT',
357 | 'INT8ARRAY',
358 | 'INT16ARRAY',
359 | 'INT32ARRAY',
360 | 'UINT8ARRAY',
361 | 'UINT16ARRAY',
362 | 'UINT32ARRAY',
363 | 'FLOAT32ARRAY',
364 | 'FLOAT64ARRAY',
365 | 'ARRAYBUFFER',
366 | 'BLOB',
367 | 'BUFFER'
368 | ];
369 | }
370 |
371 | var Length = [
372 | null, // Types.NULL
373 | null, // Types.UNDEFINED
374 | 'Uint16', // Types.STRING
375 | 'Float64', // Types.NUMBER
376 | 'Uint8', // Types.BOOLEAN
377 | null, // Types.ARRAY
378 | null, // Types.OBJECT
379 | 'Int8', // Types.INT8ARRAY
380 | 'Int16', // Types.INT16ARRAY
381 | 'Int32', // Types.INT32ARRAY
382 | 'Uint8', // Types.UINT8ARRAY
383 | 'Uint16', // Types.UINT16ARRAY
384 | 'Uint32', // Types.UINT32ARRAY
385 | 'Float32', // Types.FLOAT32ARRAY
386 | 'Float64', // Types.FLOAT64ARRAY
387 | 'Uint8', // Types.ARRAYBUFFER
388 | 'Uint8', // Types.BLOB, Types.FILE
389 | 'Uint8' // Types.BUFFER
390 | ];
391 |
392 | var binary_dump = function(view, start, length) {
393 | var table = [],
394 | endianness = BIG_ENDIAN,
395 | ROW_LENGTH = 40;
396 | table[0] = [];
397 | for (var i = 0; i < ROW_LENGTH; i++) {
398 | table[0][i] = i < 10 ? '0' + i.toString(10) : i.toString(10);
399 | }
400 | for (i = 0; i < length; i++) {
401 | var code = view.getUint8(start + i, endianness);
402 | var index = ~~(i / ROW_LENGTH) + 1;
403 | if (typeof table[index] === 'undefined') table[index] = [];
404 | table[index][i % ROW_LENGTH] = code < 16 ? '0' + code.toString(16) : code.toString(16);
405 | }
406 | console.log('%c' + table[0].join(' '), 'font-weight: bold;');
407 | for (i = 1; i < table.length; i++) {
408 | console.log(table[i].join(' '));
409 | }
410 | };
411 |
412 | var find_type = function(obj) {
413 | var type = undefined;
414 |
415 | if (obj === undefined) {
416 | type = Types.UNDEFINED;
417 |
418 | } else if (obj === null) {
419 | type = Types.NULL;
420 |
421 | } else {
422 | var const_name = obj.constructor.name;
423 | if (const_name !== undefined) {
424 | // return type by .constructor.name if possible
425 | type = Types[const_name.toUpperCase()];
426 |
427 | } else {
428 | // Work around when constructor.name is not defined
429 | switch (typeof obj) {
430 | case 'string':
431 | type = Types.STRING;
432 | break;
433 |
434 | case 'number':
435 | type = Types.NUMBER;
436 | break;
437 |
438 | case 'boolean':
439 | type = Types.BOOLEAN;
440 | break;
441 |
442 | case 'object':
443 | if (obj instanceof Array) {
444 | type = Types.ARRAY;
445 |
446 | } else if (obj instanceof Int8Array) {
447 | type = Types.INT8ARRAY;
448 |
449 | } else if (obj instanceof Int16Array) {
450 | type = Types.INT16ARRAY;
451 |
452 | } else if (obj instanceof Int32Array) {
453 | type = Types.INT32ARRAY;
454 |
455 | } else if (obj instanceof Uint8Array) {
456 | type = Types.UINT8ARRAY;
457 |
458 | } else if (obj instanceof Uint16Array) {
459 | type = Types.UINT16ARRAY;
460 |
461 | } else if (obj instanceof Uint32Array) {
462 | type = Types.UINT32ARRAY;
463 |
464 | } else if (obj instanceof Float32Array) {
465 | type = Types.FLOAT32ARRAY;
466 |
467 | } else if (obj instanceof Float64Array) {
468 | type = Types.FLOAT64ARRAY;
469 |
470 | } else if (obj instanceof ArrayBuffer) {
471 | type = Types.ARRAYBUFFER;
472 |
473 | } else if (obj instanceof Blob) { // including File
474 | type = Types.BLOB;
475 |
476 | } else if (obj instanceof Buffer) { // node.js only
477 | type = Types.BUFFER;
478 |
479 | } else if (obj instanceof Object) {
480 | type = Types.OBJECT;
481 |
482 | }
483 | break;
484 |
485 | default:
486 | break;
487 | }
488 | }
489 | }
490 | return type;
491 | };
492 |
493 | var utf16_utf8 = function(string) {
494 | return unescape(encodeURIComponent(string));
495 | };
496 |
497 | var utf8_utf16 = function(bytes) {
498 | return decodeURIComponent(escape(bytes));
499 | };
500 |
501 | /**
502 | * packs seriarized elements array into a packed ArrayBuffer
503 | * @param {Array} serialized Serialized array of elements.
504 | * @return {DataView} view of packed binary
505 | */
506 | var pack = function(serialized) {
507 | var cursor = 0,
508 | i = 0,
509 | j = 0,
510 | endianness = BIG_ENDIAN;
511 |
512 | var ab = new ArrayBuffer(serialized[0].byte_length + serialized[0].header_size);
513 | var view = new DataView(ab);
514 |
515 | for (i = 0; i < serialized.length; i++) {
516 | var start = cursor,
517 | header_size = serialized[i].header_size,
518 | type = serialized[i].type,
519 | length = serialized[i].length,
520 | value = serialized[i].value,
521 | byte_length = serialized[i].byte_length,
522 | type_name = Length[type],
523 | unit = type_name === null ? 0 : root[type_name + 'Array'].BYTES_PER_ELEMENT;
524 |
525 | // Set type
526 | if (type === Types.BUFFER) {
527 | // on node.js Blob is emulated using Buffer type
528 | view.setUint8(cursor, Types.BLOB, endianness);
529 | } else {
530 | view.setUint8(cursor, type, endianness);
531 | }
532 | cursor += TYPE_LENGTH;
533 |
534 | if (debug) {
535 | console.info('Packing', type, TypeNames[type]);
536 | }
537 |
538 | // Set length if required
539 | if (type === Types.ARRAY || type === Types.OBJECT) {
540 | view.setUint16(cursor, length, endianness);
541 | cursor += LENGTH_LENGTH;
542 |
543 | if (debug) {
544 | console.info('Content Length', length);
545 | }
546 | }
547 |
548 | // Set byte length
549 | view.setUint32(cursor, byte_length, endianness);
550 | cursor += BYTES_LENGTH;
551 |
552 | if (debug) {
553 | console.info('Header Size', header_size, 'bytes');
554 | console.info('Byte Length', byte_length, 'bytes');
555 | }
556 |
557 | switch (type) {
558 | case Types.NULL:
559 | case Types.UNDEFINED:
560 | // NULL and UNDEFINED doesn't have any payload
561 | break;
562 |
563 | case Types.STRING:
564 | if (debug) {
565 | console.info('Actual Content %c"' + value + '"', 'font-weight:bold;');
566 | }
567 | for (j = 0; j < length; j++, cursor += unit) {
568 | view.setUint16(cursor, value.charCodeAt(j), endianness);
569 | }
570 | break;
571 |
572 | case Types.NUMBER:
573 | case Types.BOOLEAN:
574 | if (debug) {
575 | console.info('%c' + value.toString(), 'font-weight:bold;');
576 | }
577 | view['set' + type_name](cursor, value, endianness);
578 | cursor += unit;
579 | break;
580 |
581 | case Types.INT8ARRAY:
582 | case Types.INT16ARRAY:
583 | case Types.INT32ARRAY:
584 | case Types.UINT8ARRAY:
585 | case Types.UINT16ARRAY:
586 | case Types.UINT32ARRAY:
587 | case Types.FLOAT32ARRAY:
588 | case Types.FLOAT64ARRAY:
589 | var _view = new Uint8Array(view.buffer, cursor, byte_length);
590 | _view.set(new Uint8Array(value.buffer));
591 | cursor += byte_length;
592 | break;
593 |
594 | case Types.ARRAYBUFFER:
595 | case Types.BUFFER:
596 | var _view = new Uint8Array(view.buffer, cursor, byte_length);
597 | _view.set(new Uint8Array(value));
598 | cursor += byte_length;
599 | break;
600 |
601 | case Types.BLOB:
602 | case Types.ARRAY:
603 | case Types.OBJECT:
604 | break;
605 |
606 | default:
607 | throw 'TypeError: Unexpected type found.';
608 | }
609 |
610 | if (debug) {
611 | binary_dump(view, start, cursor - start);
612 | }
613 | }
614 |
615 | return view;
616 | };
617 |
618 | /**
619 | * Unpack binary data into an object with value and cursor
620 | * @param {DataView} view [description]
621 | * @param {Number} cursor [description]
622 | * @return {Object}
623 | */
624 | var unpack = function(view, cursor) {
625 | var i = 0,
626 | endianness = BIG_ENDIAN,
627 | start = cursor;
628 | var type, length, byte_length, value, elem;
629 |
630 | // Retrieve "type"
631 | type = view.getUint8(cursor, endianness);
632 | cursor += TYPE_LENGTH;
633 |
634 | if (debug) {
635 | console.info('Unpacking', type, TypeNames[type]);
636 | }
637 |
638 | // Retrieve "length"
639 | if (type === Types.ARRAY || type === Types.OBJECT) {
640 | length = view.getUint16(cursor, endianness);
641 | cursor += LENGTH_LENGTH;
642 |
643 | if (debug) {
644 | console.info('Content Length', length);
645 | }
646 | }
647 |
648 | // Retrieve "byte_length"
649 | byte_length = view.getUint32(cursor, endianness);
650 | cursor += BYTES_LENGTH;
651 |
652 | if (debug) {
653 | console.info('Byte Length', byte_length, 'bytes');
654 | }
655 |
656 | var type_name = Length[type];
657 | var unit = type_name === null ? 0 : root[type_name + 'Array'].BYTES_PER_ELEMENT;
658 |
659 | switch (type) {
660 | case Types.NULL:
661 | case Types.UNDEFINED:
662 | if (debug) {
663 | binary_dump(view, start, cursor - start);
664 | }
665 | // NULL and UNDEFINED doesn't have any octet
666 | value = null;
667 | break;
668 |
669 | case Types.STRING:
670 | length = byte_length / unit;
671 | var string = [];
672 | for (i = 0; i < length; i++) {
673 | var code = view.getUint16(cursor, endianness);
674 | cursor += unit;
675 | string.push(String.fromCharCode(code));
676 | }
677 | value = string.join('');
678 | if (debug) {
679 | console.info('Actual Content %c"' + value + '"', 'font-weight:bold;');
680 | binary_dump(view, start, cursor - start);
681 | }
682 | break;
683 |
684 | case Types.NUMBER:
685 | value = view.getFloat64(cursor, endianness);
686 | cursor += unit;
687 | if (debug) {
688 | console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;');
689 | binary_dump(view, start, cursor - start);
690 | }
691 | break;
692 |
693 | case Types.BOOLEAN:
694 | value = view.getUint8(cursor, endianness) === 1 ? true : false;
695 | cursor += unit;
696 | if (debug) {
697 | console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;');
698 | binary_dump(view, start, cursor - start);
699 | }
700 | break;
701 |
702 | case Types.INT8ARRAY:
703 | case Types.INT16ARRAY:
704 | case Types.INT32ARRAY:
705 | case Types.UINT8ARRAY:
706 | case Types.UINT16ARRAY:
707 | case Types.UINT32ARRAY:
708 | case Types.FLOAT32ARRAY:
709 | case Types.FLOAT64ARRAY:
710 | case Types.ARRAYBUFFER:
711 | elem = view.buffer.slice(cursor, cursor + byte_length);
712 | cursor += byte_length;
713 |
714 | // If ArrayBuffer
715 | if (type === Types.ARRAYBUFFER) {
716 | value = elem;
717 |
718 | // If other TypedArray
719 | } else {
720 | value = new root[type_name + 'Array'](elem);
721 | }
722 |
723 | if (debug) {
724 | binary_dump(view, start, cursor - start);
725 | }
726 | break;
727 |
728 | case Types.BLOB:
729 | if (debug) {
730 | binary_dump(view, start, cursor - start);
731 | }
732 | // If Blob is available (on browser)
733 | if (root.Blob) {
734 | var mime = unpack(view, cursor);
735 | var buffer = unpack(view, mime.cursor);
736 | cursor = buffer.cursor;
737 | value = new Blob([buffer.value], {
738 | type: mime.value
739 | });
740 | } else {
741 | // node.js implementation goes here
742 | elem = view.buffer.slice(cursor, cursor + byte_length);
743 | cursor += byte_length;
744 | // node.js implementatino uses Buffer to help Blob
745 | value = new Buffer(elem);
746 | }
747 | break;
748 |
749 | case Types.ARRAY:
750 | if (debug) {
751 | binary_dump(view, start, cursor - start);
752 | }
753 | value = [];
754 | for (i = 0; i < length; i++) {
755 | // Retrieve array element
756 | elem = unpack(view, cursor);
757 | cursor = elem.cursor;
758 | value.push(elem.value);
759 | }
760 | break;
761 |
762 | case Types.OBJECT:
763 | if (debug) {
764 | binary_dump(view, start, cursor - start);
765 | }
766 | value = {};
767 | for (i = 0; i < length; i++) {
768 | // Retrieve object key and value in sequence
769 | var key = unpack(view, cursor);
770 | var val = unpack(view, key.cursor);
771 | cursor = val.cursor;
772 | value[key.value] = val.value;
773 | }
774 | break;
775 |
776 | default:
777 | throw 'TypeError: Type not supported.';
778 | }
779 | return {
780 | value: value,
781 | cursor: cursor
782 | };
783 | };
784 |
785 | /**
786 | * deferred function to process multiple serialization in order
787 | * @param {array} array [description]
788 | * @param {Function} callback [description]
789 | * @return {void} no return value
790 | */
791 | var deferredSerialize = function(array, callback) {
792 | var length = array.length,
793 | results = [],
794 | count = 0,
795 | byte_length = 0;
796 | for (var i = 0; i < array.length; i++) {
797 | (function(index) {
798 | serialize(array[index], function(result) {
799 | // store results in order
800 | results[index] = result;
801 | // count byte length
802 | byte_length += result[0].header_size + result[0].byte_length;
803 | // when all results are on table
804 | if (++count === length) {
805 | // finally concatenate all reuslts into a single array in order
806 | var array = [];
807 | for (var j = 0; j < results.length; j++) {
808 | array = array.concat(results[j]);
809 | }
810 | callback(array, byte_length);
811 | }
812 | });
813 | })(i);
814 | }
815 | };
816 |
817 | /**
818 | * Serializes object and return byte_length
819 | * @param {mixed} obj JavaScript object you want to serialize
820 | * @return {Array} Serialized array object
821 | */
822 | var serialize = function(obj, callback) {
823 | var subarray = [],
824 | unit = 1,
825 | header_size = TYPE_LENGTH + BYTES_LENGTH,
826 | type, byte_length = 0,
827 | length = 0,
828 | value = obj;
829 |
830 | type = find_type(obj);
831 |
832 | unit = Length[type] === undefined || Length[type] === null ? 0 :
833 | root[Length[type] + 'Array'].BYTES_PER_ELEMENT;
834 |
835 | switch (type) {
836 | case Types.UNDEFINED:
837 | case Types.NULL:
838 | break;
839 |
840 | case Types.NUMBER:
841 | case Types.BOOLEAN:
842 | byte_length = unit;
843 | break;
844 |
845 | case Types.STRING:
846 | length = obj.length;
847 | byte_length += length * unit;
848 | break;
849 |
850 | case Types.INT8ARRAY:
851 | case Types.INT16ARRAY:
852 | case Types.INT32ARRAY:
853 | case Types.UINT8ARRAY:
854 | case Types.UINT16ARRAY:
855 | case Types.UINT32ARRAY:
856 | case Types.FLOAT32ARRAY:
857 | case Types.FLOAT64ARRAY:
858 | length = obj.length;
859 | byte_length += length * unit;
860 | break;
861 |
862 | case Types.ARRAY:
863 | deferredSerialize(obj, function(subarray, byte_length) {
864 | callback([{
865 | type: type,
866 | length: obj.length,
867 | header_size: header_size + LENGTH_LENGTH,
868 | byte_length: byte_length,
869 | value: null
870 | }].concat(subarray));
871 | });
872 | return;
873 |
874 | case Types.OBJECT:
875 | var deferred = [];
876 | for (var key in obj) {
877 | if (obj.hasOwnProperty(key)) {
878 | deferred.push(key);
879 | deferred.push(obj[key]);
880 | length++;
881 | }
882 | }
883 | deferredSerialize(deferred, function(subarray, byte_length) {
884 | callback([{
885 | type: type,
886 | length: length,
887 | header_size: header_size + LENGTH_LENGTH,
888 | byte_length: byte_length,
889 | value: null
890 | }].concat(subarray));
891 | });
892 | return;
893 |
894 | case Types.ARRAYBUFFER:
895 | byte_length += obj.byteLength;
896 | break;
897 |
898 | case Types.BLOB:
899 | var mime_type = obj.type;
900 | var reader = new FileReader();
901 | reader.onload = function(e) {
902 | deferredSerialize([mime_type, e.target.result], function(subarray, byte_length) {
903 | callback([{
904 | type: type,
905 | length: length,
906 | header_size: header_size,
907 | byte_length: byte_length,
908 | value: null
909 | }].concat(subarray));
910 | });
911 | };
912 | reader.onerror = function(e) {
913 | throw 'FileReader Error: ' + e;
914 | };
915 | reader.readAsArrayBuffer(obj);
916 | return;
917 |
918 | case Types.BUFFER:
919 | byte_length += obj.length;
920 | break;
921 |
922 | default:
923 | throw 'TypeError: Type "' + obj.constructor.name + '" not supported.';
924 | }
925 |
926 | callback([{
927 | type: type,
928 | length: length,
929 | header_size: header_size,
930 | byte_length: byte_length,
931 | value: value
932 | }].concat(subarray));
933 | };
934 |
935 | /**
936 | * Deserialize binary and return JavaScript object
937 | * @param ArrayBuffer buffer ArrayBuffer you want to deserialize
938 | * @return mixed Retrieved JavaScript object
939 | */
940 | var deserialize = function(buffer, callback) {
941 | var view = buffer instanceof DataView ? buffer : new DataView(buffer);
942 | var result = unpack(view, 0);
943 | return result.value;
944 | };
945 |
946 | if (debug) {
947 | root.Test = {
948 | BIG_ENDIAN: BIG_ENDIAN,
949 | LITTLE_ENDIAN: LITTLE_ENDIAN,
950 | Types: Types,
951 | pack: pack,
952 | unpack: unpack,
953 | serialize: serialize,
954 | deserialize: deserialize
955 | };
956 | }
957 |
958 | var binarize = {
959 | pack: function(obj, callback) {
960 | try {
961 | if (debug) console.info('%cPacking Start', 'font-weight: bold; color: red;', obj);
962 | serialize(obj, function(array) {
963 | if (debug) console.info('Serialized Object', array);
964 | callback(pack(array));
965 | });
966 | } catch (e) {
967 | throw e;
968 | }
969 | },
970 | unpack: function(buffer, callback) {
971 | try {
972 | if (debug) console.info('%cUnpacking Start', 'font-weight: bold; color: red;', buffer);
973 | var result = deserialize(buffer);
974 | if (debug) console.info('Deserialized Object', result);
975 | callback(result);
976 | } catch (e) {
977 | throw e;
978 | }
979 | }
980 | };
981 |
982 | if (typeof module !== 'undefined' && module.exports) {
983 | module.exports = binarize;
984 | } else {
985 | root.binarize = binarize;
986 | }
987 | })(typeof global !== 'undefined' ? global : this);
988 | })();
989 |
--------------------------------------------------------------------------------