1 // wonder_core.js 2 //---------------- 3 4 5 // ------------------------------- 6 // Enums.js 7 // ------------------------------- 8 9 /** 10 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 11 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 12 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 13 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 14 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 15 * @author Kay Haensge <Kay.Haensge@telekom.de> 16 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 17 */ 18 19 /** 20 * Enumeration for the {@link Message} types 21 * @readonly 22 * @enum {string} 23 */ 24 var MessageType = { 25 /** Message to invite a peer to a conversation. */ 26 INVITATION : "invitation", 27 /** Answer for conversation accepted. */ 28 ACCEPTED : "accepted", 29 /** Message contains connectivity candidate */ 30 CONNECTIVITY_CANDIDATE : "connectivityCandidate", 31 /** Answer for conversation not accepted. */ 32 NOT_ACCEPTED : "notAccepted", 33 /** Message to cancel an invitation */ 34 CANCEL : "cancel", 35 /** Message to add a {@link Resource} to the conversation */ 36 ADD_RESOURCE : "addResource", 37 /** Message to remove a {@link Participant} from the conversation */ 38 REMOVE_PARTICIPANT : "removeParticipant", 39 /** Message to finish the communication with a peer */ 40 BYE : "bye", 41 /** Message to add a new {@link Resource} */ 42 UPDATE : "update", 43 /** Answer to add a new {@link Resource} */ 44 UPDATED : "updated", 45 46 /** Message to offer a role (TO BE IMPLEMENTED) */ 47 OFFER_ROLE : "offerRole", 48 /** Message to setup redirection (TO BE IMPLEMENTED) */ 49 REDIRECT : "redirect", 50 /** Message to remove a {@link Resource} from the conversation (TO BE IMPLEMENTED) */ 51 RESOURCE_REMOVED : "resourceRemoved", 52 /** Message to share a {@link Resource} in the conversation (TO BE IMPLEMENTED) */ 53 SHARE_RESOURCE : "shareResource" 54 }; 55 56 /** 57 * Enumeration for the {@link Participant} statuses 58 * @readonly 59 * @enum {string} 60 */ 61 var ParticipantStatus = { 62 WAITING : "waiting", 63 PENDING : "pending", 64 PARTICIPATING : "participating", 65 NOT_PARTICIPATING : "not_participating", 66 PARTICIPATED : "participated", 67 MISSED : "missed", 68 FAILED : "failed", 69 CREATED : "created", 70 ACCEPTED : "accepted" 71 }; 72 73 var ConversationStatus = { 74 OPENED : "opened", 75 INACTIVE : "inactive", 76 FAILED : "failed", 77 ACTIVE : "active", 78 CLOSED : "closed", 79 CREATED : "created", 80 RECORDING : "recording", 81 PLAYING : "playing", 82 PAUSED : "paused", 83 STOPPED : "stopped" 84 85 }; 86 87 var ConversationTopicStatus = { 88 PENDING : "pending", 89 ACTIVE : "active", 90 INACTIVE : "inactive", 91 CLOSED : "closed" 92 }; 93 94 var IdentityStatus = { 95 IDLE : "idle", 96 UNAVAILABLE : "unavailable", 97 BUSY : "busy", 98 AVAILABLE : "available", 99 ON_CONVERSATION : "onConversation" 100 }; 101 102 /** 103 * Enumeration for the {@link Resource} types 104 * @readonly 105 * @enum {string} 106 */ 107 var ResourceType = { 108 /** Webcam + microphone as source for mediastream */ 109 AUDIO_VIDEO : "audioVideo", 110 /** Microphone as source for audiostream */ 111 AUDIO_MIC : "audioMic", 112 /** Webcam as source for videostream */ 113 VIDEO_CAM : "videoCam", 114 /** Plain text chat */ 115 CHAT : "chat", 116 /** File sending (TO BE IMPLEMENTED)*/ 117 FILE : "file", 118 /** Screen content as source for videostream (TO BE IMPLEMENTED)*/ 119 SCREEN : "screen", 120 /** Other types */ 121 OTHER : "other", 122 /** Photo (TO BE IMPLEMENTED) */ 123 PHOTO : "photo", 124 /** Video (TO BE IMPLEMENTED) */ 125 VIDEO : "video", 126 /** Music (TO BE IMPLEMENTED) */ 127 MUSIC : "music" 128 }; 129 130 131 var ResourceStatus = { 132 NEW : "new", 133 SHARED : "shared", 134 NOT_SHARED : "notShared", 135 PLAYING : "playing", 136 PAUSED : "paused", 137 STOPPED : "stopped", 138 ENDED : "ended", 139 RECORDING : "recording", 140 LIVE : "live" 141 }; 142 // ------------------------------- 143 // adapter.js 144 // ------------------------------- 145 146 var RTCPeerConnection = null; 147 var getUserMedia = null; 148 var attachMediaStream = null; 149 var reattachMediaStream = null; 150 var webrtcDetectedBrowser = null; 151 var webrtcDetectedVersion = null; 152 153 function trace(text) { 154 // This function is used for logging. 155 if (text[text.length - 1] == '\n') { 156 text = text.substring(0, text.length - 1); 157 } 158 console.log((performance.now() / 1000).toFixed(3) + ": " + text); 159 } 160 161 if (navigator.mozGetUserMedia) { 162 console.log("This appears to be Firefox"); 163 164 webrtcDetectedBrowser = "firefox"; 165 166 webrtcDetectedVersion = 167 parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]); 168 169 // The RTCPeerConnection object. 170 RTCPeerConnection = mozRTCPeerConnection; 171 172 // The RTCSessionDescription object. 173 RTCSessionDescription = mozRTCSessionDescription; 174 175 // The RTCIceCandidate object. 176 RTCIceCandidate = mozRTCIceCandidate; 177 178 // Get UserMedia (only difference is the prefix). 179 // Code from Adam Barth. 180 getUserMedia = navigator.mozGetUserMedia.bind(navigator); 181 182 // Creates iceServer from the url for FF. 183 createIceServer = function(url, username, password) { 184 var iceServer = null; 185 var url_parts = url.split(':'); 186 if (url_parts[0].indexOf('stun') === 0) { 187 // Create iceServer with stun url. 188 iceServer = { 'url': url }; 189 } else if (url_parts[0].indexOf('turn') === 0 && 190 (url.indexOf('transport=udp') !== -1 || 191 url.indexOf('?transport') === -1)) { 192 // Create iceServer with turn url. 193 // Ignore the transport parameter from TURN url. 194 var turn_url_parts = url.split("?"); 195 iceServer = { 'url': turn_url_parts[0], 196 'credential': password, 197 'username': username }; 198 } 199 return iceServer; 200 }; 201 202 // Attach a media stream to an element. 203 attachMediaStream = function(element, stream) { 204 console.log("Attaching media stream"); 205 element.mozSrcObject = stream; 206 element.play(); 207 }; 208 209 reattachMediaStream = function(to, from) { 210 console.log("Reattaching media stream"); 211 to.mozSrcObject = from.mozSrcObject; 212 to.play(); 213 }; 214 215 // Fake get{Video,Audio}Tracks 216 MediaStream.prototype.getVideoTracks = function() { 217 return []; 218 }; 219 220 MediaStream.prototype.getAudioTracks = function() { 221 return []; 222 }; 223 } else if (navigator.webkitGetUserMedia) { 224 console.log("This appears to be Chrome"); 225 226 webrtcDetectedBrowser = "chrome"; 227 webrtcDetectedVersion = 228 parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]); 229 230 // Creates iceServer from the url for Chrome. 231 createIceServer = function(url, username, password) { 232 var iceServer = null; 233 var url_parts = url.split(':'); 234 if (url_parts[0].indexOf('stun') === 0) { 235 // Create iceServer with stun url. 236 iceServer = { 'url': url }; 237 } else if (url_parts[0].indexOf('turn') === 0) { 238 if (webrtcDetectedVersion < 28) { 239 // For pre-M28 chrome versions use old TURN format. 240 var url_turn_parts = url.split("turn:"); 241 iceServer = { 'url': 'turn:' + username + '@' + url_turn_parts[1], 242 'credential': password }; 243 } else { 244 // For Chrome M28 & above use new TURN format. 245 iceServer = { 'url': url, 246 'credential': password, 247 'username': username }; 248 } 249 } 250 return iceServer; 251 }; 252 253 // The RTCPeerConnection object. 254 RTCPeerConnection = webkitRTCPeerConnection; 255 256 // Get UserMedia (only difference is the prefix). 257 // Code from Adam Barth. 258 getUserMedia = navigator.webkitGetUserMedia.bind(navigator); 259 260 // Attach a media stream to an element. 261 attachMediaStream = function(element, stream) { 262 if (typeof element.srcObject !== 'undefined') { 263 element.srcObject = stream; 264 } else if (typeof element.mozSrcObject !== 'undefined') { 265 element.mozSrcObject = stream; 266 } else if (typeof element.src !== 'undefined') { 267 element.src = URL.createObjectURL(stream); 268 } else { 269 console.log('Error attaching stream to element.'); 270 } 271 }; 272 273 reattachMediaStream = function(to, from) { 274 to.src = from.src; 275 }; 276 277 // The representation of tracks in a stream is changed in M26. 278 // Unify them for earlier Chrome versions in the coexisting period. 279 if (!webkitMediaStream.prototype.getVideoTracks) { 280 webkitMediaStream.prototype.getVideoTracks = function() { 281 return this.videoTracks; 282 }; 283 webkitMediaStream.prototype.getAudioTracks = function() { 284 return this.audioTracks; 285 }; 286 } 287 288 // New syntax of getXXXStreams method in M26. 289 if (!webkitRTCPeerConnection.prototype.getLocalStreams) { 290 webkitRTCPeerConnection.prototype.getLocalStreams = function() { 291 return this.localStreams; 292 }; 293 webkitRTCPeerConnection.prototype.getRemoteStreams = function() { 294 return this.remoteStreams; 295 }; 296 } 297 } else { 298 console.log("Browser does not appear to be WebRTC-capable"); 299 } 300 301 // ------------------------------- 302 // helpfunctions.js 303 // ------------------------------- 304 305 /** 306 * @ignore 307 */ 308 309 function uuid4() { 310 return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 311 } 312 313 function guid() { 314 return uuid4() + uuid4() + '-' + uuid4() + '-' + uuid4() + '-' + uuid4() 315 + '-' + uuid4() + uuid4() + uuid4(); 316 } 317 // ------------------------------- 318 // Identity.js 319 // ------------------------------- 320 321 /** 322 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 323 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 324 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 325 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 326 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 327 * @author Kay Haensge <Kay.Haensge@telekom.de> 328 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 329 */ 330 331 /* 332 * TODO: State-machine for transitions of IdentityStatus should be described 333 * TODO: Questions about MessagingStub-Listeners 334 * Why do the addListener and removeListener methods have a Message as second param? 335 * What is the purpose? Which message type would it be? 336 */ 337 338 /** 339 * @class 340 * The Identity represents a user and contains all information needed to support 341 * Conversation services including the service endpoint to retrieve the protocol stack 342 * (Messaging Stub) that will be used to establish a signalling channel 343 * with the Identity domain messaging server. 344 * </br><b>NOTE:</b> Identities are only created by using the corresponding create-methods of the Idp. 345 * This constructor will throw an exception if it is used directly. 346 */ 347 function Identity(rtcIdentity, idpRtcIdentity) { 348 349 if ( rtcIdentity ) 350 throw "Illegal attempt to create an Identity --> Please use Idp.getInstance().createIdentity(...) instead." 351 352 // We use a string, RTCIdentityAssertion is not available. 353 this.rtcIdentity = idpRtcIdentity; 354 this.id = new Date().getTime(); 355 console.log( "####################"); 356 console.log( "created new Identiy for: " + this.rtcIdentity + " with id = " + this.id); 357 console.trace(); 358 console.log( "####################"); 359 this.idp = ""; 360 this.status = IdentityStatus.IDLE; // not initialized yet TODO: do state changes in a central place 361 362 this.messagingStubLibUrl = ""; 363 this.messagingStub; 364 this.notificationAddress; 365 this.messagingAddress; 366 this.credentials; 367 this.tone; 368 this.avatar; 369 }; 370 371 /** 372 * This method downloads a messaging stub and keeps a reference to it in a local 373 * attribute, if not already done before. That means the download will only be performed once. 374 * After download it invokes the given callback with a reference to the downloaded MessagingStub. 375 * 376 * @param callback {callback(MessagingStub)} callback that is invoked with messagingStub as param; if download failed then the stub param is empty 377 */ 378 Identity.prototype.resolve = function( callback ) { 379 var that = this; 380 console.log( "resolving identity: " + this.rtcIdentity); 381 if ( ! this.messagingStub.impl ) { 382 // not resolved yet --> let's ask Idp for a stub with the same downloadUri 383 var knownStub = Idp.getInstance().getResolvedStub(this.messagingStubLibUrl); 384 if ( knownStub) { 385 this.messagingStub = knownStub; 386 callback(this.messagingStub); 387 return; 388 } 389 390 console.log( "downloading Messaging stub from: " + this.messagingStubLibUrl ); 391 392 // parse the downloadURL to get the name of the Stub 393 var pathArr = this.messagingStubLibUrl.split("/"); 394 var stubName = pathArr[pathArr.length-1]; 395 stubName = stubName.substring(0, stubName.lastIndexOf(".")); 396 console.log("stub-name is: " + stubName ); 397 398 var check = function(stub, callback, count) { 399 if ( typeof(window[stub]) == "function" ) { 400 // instantiate stub 401 var messagingStub = new window[stub](); 402 // should be an object now 403 if ( typeof(messagingStub) == "object" ) { 404 // assign the new messagingStub object to the "impl" field of the container stub 405 that.messagingStub.setImpl(messagingStub); 406 that.messagingStub.message = "stub downloaded from: " + that.messagingStubLibUrl; 407 // return container-stub in callback 408 callback(that.messagingStub); 409 } 410 } 411 else { 412 count++; 413 if ( count < 20 ) 414 setTimeout( check, 500, stub, callback, count ); 415 else { 416 callback(); 417 } 418 } 419 }; 420 this.loadJSfile( this.messagingStubLibUrl ); 421 setTimeout( check, 100, stubName, callback, 0 ); 422 } 423 else { 424 console.log( this.rtcIdentity + ": no need to download stub from: " + this.messagingStubLibUrl); 425 callback( this.messagingStub ); 426 } 427 }; 428 429 430 /**@ignore 431 * 432 * @param subscriber : 433 * Identity ... The identity of the subscriber 434 */ 435 Identity.prototype.subscribe = function(subscriber) { 436 // TODO: This is presence related stuff --> postponed 437 }; 438 439 /**@ignore 440 * publish 441 * 442 * @param status : 443 * String ... The status to publish 444 * @param target : 445 * Identity [] ... The identity/identities to publish the status to 446 * @param context : 447 * String ... The context 448 * 449 */ 450 Identity.prototype.publish = function(status, target, context) { 451 // TODO: This is presence related stuff --> postponed 452 // TODO Send a publish message 453 }; 454 455 /**@ignore 456 * getStatus 457 * 458 * @returns IdentityStatus ... gets the status attribute for a participant 459 * 460 */ 461 Identity.prototype.getStatus = function() { 462 return this.status; 463 }; 464 465 /**@ignore 466 * getMessagingStubDownloadUrl 467 * 468 * @returns IdentityStatus ... gets the status attribute for a participant 469 * 470 */ 471 Identity.prototype.getMessagingStubDownloadUrl = function() { 472 //will be needed to the Idp 473 return this.messagingStubDownloadUrl; 474 }; 475 476 477 /**@ignore 478 * 479 * OnLastMessagingListerner is invoked by the MessagingStub as soon as the last listener has un-subscribed. 480 * We use this callback to disconnect and unload the MessagingStub. 481 * 482 */ 483 Identity.prototype.onLastMessagingListener = function() { 484 if ( this.messagingStub ) 485 this.messagingStub.disconnect(); 486 // "undefine" messagingStub 487 delete this.messagingStub; 488 }; 489 490 /**@ignore 491 * Simple function to load an external Javascript file at runtime. 492 * TODO: This might be a security risk, because the file is executed immediately. 493 * @param url ... url of the file to be loaded 494 */ 495 Identity.prototype.loadJSfile = function(url) { 496 var fileref = document.createElement('script'); 497 fileref.setAttribute("type", "text/javascript"); 498 fileref.setAttribute("src", url); 499 if (typeof fileref != "undefined") 500 document.getElementsByTagName("head")[0].appendChild(fileref); 501 }; 502 503 504 // ------------------------------- 505 // Idp.js 506 // ------------------------------- 507 508 /** 509 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 510 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 511 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 512 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 513 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 514 * @author Kay Haensge <Kay.Haensge@telekom.de> 515 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 516 */ 517 518 /** 519 * The Idp is a singleton object, there will always be just one instance of it, 520 * no matter how often the constructor is called. 521 * @class 522 */ 523 function Idp(rtcIdentity, options) { 524 525 // ensure that there is always just a singleton instance of the Idp, 526 // no matter how often the constructor is called 527 if (arguments.callee.singleton) { 528 return arguments.callee.singleton; 529 } 530 arguments.callee.singleton = this; 531 532 if ( ! options ) 533 options = {}; 534 //needs to know the domain of my identity 535 var that = this; 536 this.domain = options.domain || '150.140.184.246'; 537 this.port = options.port || '28017'; 538 this.protocol = options.protocol || 'https'; 539 this.path = options.path || ''; 540 this.messagingstubs = []; 541 this.identities = []; // to collect all created Identities [ messagingAddress , Identity] 542 this.pendingIdentities = {}; 543 this.ownMessagingLibUrl; 544 this.ownRtcIdentity = rtcIdentity; 545 // initialize with a new MessagingStub delegator/container 546 this.myOwnMessagingStub = new MessagingStub(); 547 console.log("created idp with domain:port: " + this.domain + ":" + this.port); 548 } 549 550 /** 551 * This is a getter for an already created instance of the IDP. 552 * The params are optional. In case there was no instance already created before, 553 * the params can also be given here and will then be used for initial creation of the object. 554 */ 555 Idp.getInstance = function(rtcIdentity, options) { 556 return new Idp(rtcIdentity, options); 557 }; 558 559 560 Idp.prototype.checkForIdentity = function(rtcIdentity) { 561 // do we already have this rtcIdentity in the identities array ? 562 for (var i = 0; i < this.identities.length; i++) { 563 if (rtcIdentity == this.identities[i].identity.rtcIdentity) { 564 return this.identities[i].identity; 565 } 566 } 567 return null; 568 }; 569 570 Idp.prototype.getResolvedStub = function(downloadUri) { 571 // do we already have a stub for the same downloadUri with impl != undefined 572 for (var i = 0; i < this.messagingstubs.length; i++) { 573 if (downloadUri == this.messagingstubs[i].name) { 574 if ( this.messagingstubs[i].messagingStub.impl) 575 return this.messagingstubs[i].messagingStub; 576 } 577 } 578 return null; 579 }; 580 581 /** 582 * This method takes either a single rtcIdentity or an array of rtcIdentities and 583 * creates Identity objects from them. The successfully created Identities are 584 * then returned in an Array in the success callback. 585 * If one or more rtcIdentities can't be created then the returned array is shorter 586 * than the given array. 587 */ 588 Idp.prototype.createIdentities = function(rtcIdentities, onSuccessCallback, onErrorCallback) { 589 console.log("Idp > createIdentities."); 590 var results = []; 591 var ids = []; 592 var count = 0; 593 var that = this; 594 595 if (! (rtcIdentities instanceof Array)) { 596 ids.push(rtcIdentities); 597 } 598 else { 599 ids = rtcIdentities; 600 } 601 602 var internalSuccessCallback = function(identity) { 603 count++; 604 results.push(identity); 605 if(count < ids.length) 606 that.createIdentity(ids[count], internalSuccessCallback, internalErrorCallback); 607 else //if(count == ids.length){ 608 onSuccessCallback(results); 609 610 }; 611 612 var internalErrorCallback = function() { 613 console.log("Idp > internalErrorCallback error"); 614 count++; 615 if (count == ids.length) 616 onSuccessCallback(results); 617 } 618 619 620 this.createIdentity(ids[0], internalSuccessCallback, internalErrorCallback); 621 }; 622 623 624 Idp.prototype.createIdentity = function(rtcIdentity, onSuccessCallback, onErrorCallback) { 625 626 // handle potentially wrong usage 627 // check for the possibility that the given rtcIdentity is not a String but already of type identity 628 if (rtcIdentity instanceof Identity) 629 // if yes return it and stop 630 return rtcIdentity; 631 632 // handle potentially wrong usage 633 if (rtcIdentity instanceof Array) { 634 throw "Wrong usage. Don't call createIdentity() with an array as first param --> use createIdentities() instead" 635 return; 636 } 637 638 // does the Idp already know this identity ? 639 var that = this; 640 var identity = this.checkForIdentity(rtcIdentity); 641 642 // if identity already known, return it and stop 643 if (identity) { 644 onSuccessCallback(identity); 645 return; 646 } 647 648 var pendingIdentity = this.pendingIdentities[rtcIdentity]; 649 650 console.log("pendingIdentity: " + pendingIdentity); 651 if ( pendingIdentity && rtcIdentity == pendingIdentity.rtcIdentity ) { 652 console.log("pendingIdentity matches rtcIdentity: " + rtcIdentity + " --> returning it"); 653 onSuccessCallback(pendingIdentity); 654 return; 655 } 656 console.log("no matching pendingIdentity --> creating new one"); 657 pendingIdentity = new Identity(null, rtcIdentity); 658 this.pendingIdentities[rtcIdentity] = pendingIdentity; 659 660 // do a lookup in the IDP-DB 661 loadJSfile('http://' + this.domain + ':' + this.port + '/webrtc/users/?filter_rtcIdentity=' + rtcIdentity +'&jsonp=returnIdentity'); 662 //loadJSfile(this.protocol + '://' + this.domain + ':' + this.port + '/' + this.path + rtcIdentity); 663 664 // @callback function in the url 665 returnIdentity = function(data) { 666 //see if the identity exists 667 if (data.total_rows > 0) { 668 var localStubURL = data.rows[0].localMsgStubURL; 669 var generalStubURL = data.rows[0].messagingStubURL; 670 671 // first identity is expected to be the own identity !? 672 if (that.identities.length <= 0) { 673 // use local stub url, if available, general stub if no local available 674 if (localStubURL) { 675 console.log("found localMsgStubURL for IDP owning Identity: " + localStubURL); 676 that.ownMessagingLibUrl = localStubURL; 677 } 678 else 679 that.ownMessagingLibUrl = generalStubURL; 680 } 681 682 var existStub = false; 683 var index = null; 684 685 // invoke without rtcIdentity, otherwise exception will be thrown 686 //identity = new Identity(null, data.rows[0].rtcIdentity); 687 identity = pendingIdentity; 688 689 // if new identity has the same local stub url as the idp itself, then use this one 690 if (localStubURL === that.ownMessagingLibUrl) { 691 console.log("use localMsgStubURL for new Identity: " + localStubURL); 692 identity.messagingStubLibUrl = that.ownMessagingLibUrl; 693 } 694 else 695 identity.messagingStubLibUrl = generalStubURL; 696 697 //create the identity with the right fields 698 identity.messagingAddress = data.rows[0].messagingAddress; 699 identity.idp = that; 700 701 identity.credentials = { 702 "username": data.rows[0].rtcIdentity, 703 "password": data.rows[0].password 704 }; 705 706 //if it is equal then messagingStub of remote equals to mine 707 if (typeof that.ownMessagingLibUrl !== 'undefined') { 708 if (that.ownMessagingLibUrl == identity.messagingStubLibUrl) { 709 identity.messagingStub = that.myOwnMessagingStub; 710 } else { 711 for (var i = 0; i < that.messagingstubs.length; i++) { 712 //compare if already exist 713 if (that.messagingstubs[i].name == identity.messagingStubLibUrl) { 714 existStub = true; 715 index = i; 716 break; 717 } 718 } 719 if (existStub) { 720 //if exist the stub then identity stub equals to the exist one 721 identity.messagingStub = that.messagingstubs[index].messagingStub; 722 } else { 723 // @pchainho TODO should only instantiate a general messagingStub, the MessagingStub lib is not downloaded at this point 724 var stub = new MessagingStub(); 725 identity.messagingStub = stub; 726 that.messagingstubs.push({ 727 "name": identity.messagingStubLibUrl, 728 "messagingStub": stub //put the general messagingstub in a way that can be shared to the other clients and then use It 729 }); 730 } 731 } 732 } 733 that.identities.push({ 734 "messagingAddress": data.rows[0].messagingAddress, 735 "identity": identity 736 }); 737 if ( pendingIdentity && rtcIdentity == pendingIdentity.rtcIdentity ) { 738 console.log("cleaning up pendingIdentity"); 739 delete that.pendingIdentities[rtcIdentity]; 740 } 741 onSuccessCallback(identity); 742 } else { 743 onErrorCallback(); 744 } 745 }; 746 } 747 748 // @pchainho retrieve Identity from its Messaging Address. Needed to process received messages in the Stub 749 750 Idp.prototype.getIdentity = function(messageAddress) { 751 for (var i = 0; i < this.identities.length; i++) { 752 if (this.identities[i].messagingAddress === messageAddress) 753 return this.identities[i].identity; 754 } 755 756 } 757 //load the file with the information about an identity 758 loadJSfile = function(url) { 759 var fileref = document.createElement('script'); 760 fileref.setAttribute("type", "text/javascript"); 761 fileref.setAttribute("src", url); 762 if (typeof fileref != "undefined") 763 document.getElementsByTagName("head")[0].appendChild(fileref); 764 }; 765 766 767 768 /* 769 This class implement the creation of remote identities and the creation of me 770 771 In the creation of this.me: 772 this.createIdentity will be called and create an identity with the parameters that are retrieved on the "GET" operation. 773 After that do a callback function with the identity information as a parameter. 774 Then: this.me = identity; and do the resolve. I added a the listener parameter to this identity add the application listener to his identity. 775 and then connects to the stub that is returned by the resolve. 776 777 778 For remote identities. will verify if this.me is already defined if it is not callbackerror (not implemented yet), 779 if this.me is defined then will create the identity with the fields retrieved from the idpserver and then add the stub 780 to this.messagingstubs and the identity with the correspondent messagingServer in the this.identities array. 781 782 The getIdentity is not working yet <- fix it soon */ 783 784 // ------------------------------- 785 // Message.js 786 // ------------------------------- 787 788 /** 789 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 790 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 791 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 792 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 793 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 794 * @author Kay Haensge <Kay.Haensge@telekom.de> 795 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 796 */ 797 798 799 /** 800 * @class 801 * Message - This class is a data-holder for all messages that are sent between the domains. 802 * @param {Identity} from - Sender of the message 803 * @param {Identity[]} [to] - Recipients of the message 804 * @param {MessageBody} body - Message body (a json struct) 805 * @param {MessageType} type - Type of the Message (@see MessageType) 806 * @param {string} [context] - ID of the conversation. (Optional. For conversation related messages it is mandatory.) 807 */ 808 function Message(from, to, body, type, context) { 809 // generate unique id for this message 810 this.id = guid(); 811 this.from = from; 812 this.to = to; 813 this.body = body; 814 this.type = type; 815 816 // Optional. For conversation related messages it is mandatory 817 this.contextId = context; 818 819 this.reply_to_uri; 820 this.previous; 821 } 822 823 /** 824 * @ignore 825 * 826 * newReplyMessage - This is a special factory method for a "reply-message". 827 * It takes a previous message and swaps their from and to fields. 828 * 829 * @param {MessageBody} body - Message body (a json struct) 830 * @param {Message} previousMessage - Message to generate the reply from. 831 * @param {MessageType} type - Message type. 832 * @param {Identity} me - reply_to_uri identity. 833 */ 834 Message.prototype.newReplyMessage = function(body, previousMessage, type, me) { 835 if (!previousMessage || !body || !type) 836 return; 837 // create a new message with swapped from and to fields (taken from previous message) 838 // DONE: take Myself as from, take all previous.to - ME plus original sender as to 839 var to = new Array(); 840 841 // Take all previous to - ME 842 previousMessage.to.every(function(element, index, array) { 843 if (element != me) 844 to.push(element); 845 }); 846 847 // + original sender 848 to.push(previousMessage.from); 849 850 var rm = new Message(me, to, body, type); 851 rm.previous = previousMessage; 852 rm.reply_to_uri = me; 853 return (rm); 854 }; 855 // ------------------------------- 856 // MessageFactory.js 857 // ------------------------------- 858 859 /** 860 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 861 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 862 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 863 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 864 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 865 * @author Kay Haensge <Kay.Haensge@telekom.de> 866 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 867 */ 868 869 870 /** 871 * @class 872 * This class creates WONDER-compliant messages. Please note that all functions in this class are static, so there is no need to create MessageFactory objects. 873 */ 874 function MessageFactory() { 875 } 876 877 878 /** 879 * createInvitationMessage - Creates an Invitation message, the connectionDescription field will be empty and has to be filled before sending. 880 * 881 * 882 * @param {Identity} from - The {@link Identity} that figures as sender of the message. 883 * @param {Identity[]} to - The Array of {@link Identity} that figures as receiver of the message. 884 * @param {string} contextId - The contextId of the conversation related to the invitation. 885 * @param {ResourceConstraints} constraints - The resource constraints for the resources initialized on conversation start. 886 * @param {string} [conversationURL] - The URL of the conversation (optional). 887 * @param {string} [subject] - The subject of the conversation. (optional). 888 * @param {Identity} [hosting] - The host of the conversation (optional). [NOT IMPLEMENTED, by default the host will be the one starting the conversation] 889 * @return The created Message 890 * 891 */ 892 MessageFactory.createInvitationMessage = function(from, to, contextId, constraints, conversationURL, subject, hosting, agenda, peers) { 893 var invitationBody = new Object(); 894 invitationBody.conversationURL = conversationURL; 895 invitationBody.connectionDescription = ""; 896 invitationBody.subject = subject; 897 invitationBody.hosting = hosting; 898 invitationBody.agenda = agenda; 899 invitationBody.peers = peers; 900 invitationBody.constraints = constraints; 901 902 var invitationMessage = new Message(from, to, invitationBody, MessageType.INVITATION, contextId); 903 return invitationMessage; 904 } 905 906 /** 907 * createAnswerMessage - Creates an Answer message, the connectionDescription field will be empty and has to be filled before sending. 908 * 909 * @param {Identity} from - The {@link Identity} that figures as sender of the message. 910 * @param {Identity[]} to - The Array of {@link Identity} that figures as receiver of the message. 911 * @param {string} contextId - The contextId of the conversation related to the invitation. 912 * @param {ResourceConstraints} constraints - The resource constraints for the resources initialized on conversation start. 913 * @param {Identity} [hosting] - The host of the conversation (optional). [NOT IMPLEMENTED, by default the host will be the one starting the conversation] 914 * @param {Identity[]} connected - Array of {@link Identity} that are already connected to the conversation. Used to establish the order in the connection flow for multiparty. 915 * @return The created Message 916 * 917 */ 918 MessageFactory.createAnswerMessage = function(from, to, contextId, constraints, hosting, connected) { 919 var answerBody = new Object(); 920 answerBody.connectionDescription = ""; 921 answerBody.hosting = hosting; 922 answerBody.connected = connected; 923 answerBody.constraints = constraints; 924 925 var answerMessage = new Message(from,to,answerBody,MessageType.ACCEPTED,contextId); 926 return answerMessage; 927 } 928 929 /** 930 * createCandidateMessage - Creates a Message containing an ICE candidate 931 * 932 * @param {Identity} from - The {@link Identity} that figures as sender of the message. 933 * @param {Identity[]} to - The Array of {@link Identity} that figures as receiver of the message. 934 * @param {string} contextId - The contextId of the conversation related to the invitation. 935 * @param {string} label - The label of the candidate. 936 * @param {string} id - The id of the candidate. 937 * @param {string} candidate - The ICE candidate string. 938 * @param {boolean} lastCandidate - Boolean indicating if the candidate is the last one. If true, include the full SDP in the candidate parameter for compatibility with domains that don't support trickling. 939 * @return The created Message 940 * 941 */ 942 943 MessageFactory.createCandidateMessage = function (from, to, contextId, label, id, candidate, lastCandidate) { 944 if (lastCandidate == false) { 945 var candidateBody = new Object(); 946 candidateBody.label = label; 947 candidateBody.id = id; 948 candidateBody.candidateDescription = candidate; 949 candidateBody.lastCandidate = false; 950 } 951 else{ 952 var candidateBody = new Object(); 953 candidateBody.label = label; 954 candidateBody.id = id; 955 candidateBody.candidateDescription = ""; 956 candidateBody.connectionDescription = candidate; 957 candidateBody.lastCandidate = true; 958 } 959 960 var candidateMessage = new Message(from,to,candidateBody,MessageType.CONNECTIVITY_CANDIDATE,contextId); 961 return candidateMessage; 962 } 963 964 965 966 /** 967 * createUpdateMessage - Creates an Update message, the newConnectionDescription field will be empty and has to be filled before sending. 968 * 969 * 970 * @param {Identity} from - The {@link Identity} that figures as sender of the message. 971 * @param {Identity[]} to - The Array of {@link Identity} that figures as receiver of the message. 972 * @param {string} contextId - The contextId of the conversation related to the invitation. 973 * @param {ResourceConstraints} newConstraints - The resource constraints for the resources to update. 974 * @return The created Message 975 * 976 */ 977 MessageFactory.createUpdateMessage = function(from, to, contextId, newConstraints) { 978 var updatebody = new Object(); 979 updatebody.newConnectionDescription = ""; 980 updatebody.newConstraints = newConstraints; 981 //updatebody.hosting = hosting; 982 //updatebody.agenda = agenda; 983 //updatebody.dataCodecs = dataCodecs; 984 985 var updateMessage = new Message(from, to, updatebody, MessageType.UPDATE, contextId); 986 return updateMessage; 987 } 988 989 990 /** 991 * createUpdatedMessage - Creates an Updated message, the newConnectionDescription field will be empty and has to be filled before sending. 992 * 993 * 994 * @param {Identity} from - The {@link Identity} that figures as sender of the message. 995 * @param {Identity[]} to - The Array of {@link Identity} that figures as receiver of the message. 996 * @param {string} contextId - The contextId of the conversation related to the invitation. 997 * @param {ResourceConstraints} newConstraints - The resource constraints for the resources to update. 998 * @return The created Message 999 * 1000 */ 1001 MessageFactory.createUpdatedMessage = function(from, to, contextId, newConstraints) { 1002 var updatebody = new Object(); 1003 updatebody.newConnectionDescription = ""; 1004 updatebody.newConstraints = newConstraints; 1005 //updatebody.hosting = hosting; 1006 //updatebody.agenda = agenda; 1007 //updatebody.dataCodecs = dataCodecs; 1008 1009 var updateMessage = new Message(from, to, updatebody, MessageType.UPDATED, contextId); 1010 return updateMessage; 1011 } 1012 1013 1014 MessageFactory.createNotAccepted = function(message) { 1015 var notAcceptedMessage = new Message(message.to[0], message.from,"",MessageType.NOT_ACCEPTED, message.contextId); 1016 return notAcceptedMessage; 1017 } 1018 1019 1020 // ------------------------------- 1021 // MessagingStub.js 1022 // ------------------------------- 1023 1024 /** 1025 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1026 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1027 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1028 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1029 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1030 * @author Kay Haensge <Kay.Haensge@telekom.de> 1031 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1032 */ 1033 1034 /** 1035 * 1036 * @class 1037 * The MessagingStub implements the the protocol Stack used to communicate with a certain Messaging server. 1038 * It defines a set of methods that must be implemented in order to support a new domain. 1039 * 1040 */ 1041 function MessagingStub() { 1042 this.impl = null; 1043 this.message = "No implementation downloaded and assigned to this stub yet!"; 1044 1045 // do the listener handling already here 1046 this.listeners = new Array(new Array(), new Array(), new Array()); 1047 this.buffer = new Array(); 1048 } 1049 1050 1051 MessagingStub.prototype.setImpl = function(stubImplementation) { 1052 this.impl = stubImplementation; 1053 // put a ref to the base stub into the impl 1054 this.impl.baseStub = this; 1055 }; 1056 1057 /** @ignore 1058 * 1059 * addListener - Adds a listener. If the listener exists with the same contextID, doesn't add it again. 1060 * @param {StubEvtHandler} listener - Listener to execute its do(message) when a new message arrives. 1061 * @param {URI} [rtcIdentity] - The RTCIdentity of the Identity to be notified. (optional) 1062 * @param {string} [contextId] - The ID of the context to be notified. If not specified it will receive invitation messages and messages without contextID. (optional) 1063 */ 1064 MessagingStub.prototype.addListener = function(listener, rtcIdentity, contextId) { 1065 // Checks that the listener is not on the list with the same context already 1066 var index = 0; 1067 while (this.listeners[0].indexOf(listener, index) != -1) { 1068 index = this.listeners[0].indexOf(listener, index); 1069 if (this.listeners[2][index] == contextId && this.listeners[1][index] == rtcIdentity) 1070 return; 1071 } 1072 1073 // Adds the listener with the contextID to the listeners list. 1074 this.listeners[0].push(listener); 1075 this.listeners[1].push(rtcIdentity); 1076 this.listeners[2].push(contextId); 1077 1078 for (var i = this.buffer.length - 1; i > -1; i--) { 1079 var message = this.buffer[i]; 1080 var filtered_listeners = []; 1081 var idx = this.listeners[1].indexOf(message.from.rtcIdentity); 1082 while (idx != -1) { 1083 if (this.listeners[2][idx] == message.contextId) 1084 filtered_listeners.push(this.listeners[0][idx]); 1085 idx = this.listeners.indexOf(message.from.rtcIdentity, idx + 1); 1086 } 1087 filtered_listeners.forEach(function(element, index, array) { 1088 element(message); 1089 }); 1090 if (filtered_listeners.length != 0) 1091 this.buffer.splice(i, 1); 1092 } 1093 }; 1094 1095 1096 /** 1097 * sendMessage - Sends the specified message. 1098 * @param {Message} message - Message to send. 1099 */ 1100 MessagingStub.prototype.sendMessage = function(message) { 1101 if (this.impl) { 1102 this.impl.sendMessage(message); 1103 } 1104 else { 1105 console.log(this.message); 1106 } 1107 }; 1108 1109 1110 /** 1111 * @ignore 1112 * 1113 * removeListener - Removes a listener. 1114 * @param {StubEvtHandler} listener - Listener to execute its do(message) when a new message arrives. 1115 * @param {URI} [rtcIdentity] - The RTCIdentity of the Identity. (required only if the listener to remove included it) 1116 * @param {string} [contextId] - The ID of the context. (required only if the listener to remove included it) 1117 */ 1118 MessagingStub.prototype.removeListener = function (listener, rtcIdentity, contextId) { 1119 var index = 0; 1120 if (!listener && !contextId) { 1121 while (this.listeners[1].indexOf(rtcIdentity, index) != -1) { 1122 index = this.listeners[1].indexOf(rtcIdentity, index); 1123 this.listeners[0].splice(index, 1); 1124 this.listeners[1].splice(index, 1); 1125 this.listeners[2].splice(index, 1); 1126 } 1127 } else { 1128 1129 1130 while (this.listeners[0].indexOf(listener, index) != -1) { 1131 index = this.listeners[0].indexOf(listener, index); 1132 if (this.listeners[2][index] == contextId && this.listeners[1][index] == rtcIdentity) { 1133 this.listeners[0].splice(index, 1); 1134 this.listeners[1].splice(index, 1); 1135 this.listeners[2].splice(index, 1); 1136 break; // Because in addListener already checks that there is only one. 1137 } 1138 } 1139 } 1140 }; 1141 1142 /** 1143 * Creates the connection, connects to the server and establish the callback to the listeners on new message. 1144 * @param {URI} ownRtcIdentity - URI with the own RTCIdentity used to connect to the Messaging Server. 1145 * @param {Object} credentials - Credentials to connect to the server. 1146 * @param {callback} callbackFunction - Callback to execute when the connection is done. 1147 */ 1148 MessagingStub.prototype.connect = function(ownRtcIdentity, credentials, callbackFunction, errorCallbackFunction) { 1149 if (this.impl) { 1150 this.impl.connect(ownRtcIdentity, credentials, callbackFunction, errorCallbackFunction); 1151 } 1152 else { 1153 console.log(this.message); 1154 } 1155 }; 1156 1157 1158 /** 1159 * disconnect - Disconnects from the server. 1160 */ 1161 MessagingStub.prototype.disconnect = function() { 1162 if (this.impl) { 1163 this.impl.disconnect(); 1164 } 1165 else { 1166 console.log(this.message); 1167 } 1168 }; 1169 1170 1171 /** 1172 * @ignore 1173 * 1174 * getListeners - Gets the list of listeners. 1175 * @returns [listener[], rtcIdentity[], contextID[]] Returns an 2-D array of listeners, rtcIdentities and contextIDs 1176 */ 1177 MessagingStub.prototype.getListeners = function() { 1178 return this.listeners; 1179 }; 1180 1181 /** 1182 * @ignore 1183 */ 1184 MessagingStub.prototype.sendOtherMessages = function(message){ 1185 1186 var filtered_listeners = []; 1187 var idx = this.listeners[1].indexOf(message.from.rtcIdentity); 1188 while (idx != -1) { 1189 if (this.listeners[2][idx] == message.contextId) 1190 filtered_listeners.push(this.listeners[0][idx]); 1191 idx = this.listeners.indexOf(message.from.rtcIdentity, idx + 1); 1192 } 1193 filtered_listeners.every(function(element, index, array) { 1194 element(message); 1195 }); 1196 if (filtered_listeners.length == 0) { 1197 this.buffer.push(message); 1198 } 1199 } 1200 1201 /** 1202 * @ignore 1203 */ 1204 MessagingStub.prototype.deliverMessage = function(message) { 1205 console.log("S->C: ", message); 1206 // Filter the listeners to redirect the message 1207 var that = this; 1208 1209 if (message.type == MessageType.INVITATION || !message.contextId) 1210 { 1211 if(this.listeners[0].length == 1){ 1212 console.log("Registered an Handler: ", message); 1213 var filtered_listeners = []; 1214 var idx = this.listeners[2].indexOf(undefined); 1215 while (idx != -1) { 1216 filtered_listeners.push(this.listeners[0][idx]); 1217 idx = this.listeners.indexOf("", idx + 1); 1218 } 1219 filtered_listeners.every(function(element, index, array) { 1220 element(message); 1221 }); 1222 1223 }else{ 1224 this.sendOtherMessages(message); 1225 } 1226 //regist an handler with contextId 1227 } 1228 else 1229 { 1230 this.sendOtherMessages(message); 1231 } 1232 1233 }; 1234 1235 // ------------------------------- 1236 // Codec.js 1237 // ------------------------------- 1238 1239 /** 1240 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1241 * @author <a href="mailto:paulo-g-chainho@ptinovacao.pt">Paulo Chainho</a> 1242 * @author <a href="mailto:Steffen.Druesedow@telekom.de">Steffen Druesedow</a> 1243 * @author <a href="mailto:Miguel.Seijo@telekom.de">Miguel Seijo</a> 1244 * @author <a href="mailto:vasco-m-amaral@ptinovacao.pt">Vasco Amaral</a> 1245 * @author <a href="mailto:Kay.Haensge@telekom.de">Kay Haensge</a> 1246 * @author <a href="mailto:luis-f-oliveira@ptinovacao.pt">Luis Oliveira</a> 1247 */ 1248 1249 1250 /** 1251 * @class 1252 * The Codec is used by Resources that are shared on top of the Data Channel, 1253 * like file sharing and Textual Chat, to decode and code the data 1254 * in a consistent way by all the peers. The Data Codec may also be 1255 * downloaded on the fly by the peers. 1256 * 1257 */ 1258 1259 function Codec(type, CodecLibUrl, dataBroker){ 1260 this.listeners = []; 1261 this.id = guid(); 1262 this.type = type; 1263 this.description; 1264 this.CodecLibUrl = CodecLibUrl; 1265 this.mime_type; 1266 this.dataBroker = dataBroker; 1267 this.chunkLength = 1000; 1268 this.arrayToStoreChunks = []; 1269 } 1270 1271 1272 /** 1273 * Sends a Message from channel in the DataBroker. 1274 * 1275 * @param {String} - String with the content of a message, is a simple string even for chat and file. 1276 * 1277 */ 1278 1279 Codec.prototype.send = function( input ){ 1280 var aux = JSON.parse(input); 1281 //How to change the mimetype of input.body is equal to this.mimetype 1282 if(!this.dataBroker) 1283 return; 1284 if(this.type=="chat"){ 1285 this.dataBroker.send(input); 1286 }else{ // case file Sharing... 1287 var reader = new window.FileReader(); 1288 1289 // get file from system 1290 var fileElement = document.getElementById(aux.body); 1291 var file = fileElement.files[0]; 1292 var thatCodec = this; 1293 var readFile = function(event,text){ 1294 var data = {}; // data object to transmit over data channel 1295 1296 if (event) text = event.target.result; // on first invocation 1297 1298 if (text.length > 1000) { 1299 data.message = text.slice(0, 1000); // getting chunk using predefined chunk length 1300 } else { 1301 data.message = text; 1302 data.last = true; 1303 } 1304 aux.body = data; 1305 thatCodec.dataBroker.send(JSON.stringify(aux)); 1306 1307 var remainingDataURL = text.slice(data.message.length); 1308 if (remainingDataURL.length) setTimeout(function () { 1309 readFile(null, remainingDataURL); // continue transmitting 1310 }, 500) 1311 }; 1312 reader.readAsDataURL(file); 1313 reader.onload =readFile; 1314 } 1315 1316 } 1317 1318 /** 1319 * getReport function. 1320 * @param.. reportHandler. 1321 */ 1322 1323 Codec.prototype.getReport = function( reportHandler ){ 1324 1325 1326 } 1327 1328 1329 /** 1330 * Receives a Message from channel that is designated to this codec to present it in the application. 1331 * 1332 * @param {JsonObject} - JsonObject with the content of a message, is a DataMessage Type. 1333 * 1334 */ 1335 1336 Codec.prototype.onData = function( dataMsg ){ 1337 1338 //take data and treat it 1339 console.log(this.listeners); 1340 if(this.type=="chat"){ 1341 this.listeners.every(function(element, index, array){ 1342 element('ondatamessage',dataMsg); 1343 }); 1344 }else{ 1345 //var data = JSON.parse(dataMsg.body.message); 1346 this.arrayToStoreChunks.push(dataMsg.body.message); 1347 if (dataMsg.body.last) { 1348 this.saveToDisk(this.arrayToStoreChunks.join(''), 'fileName'); 1349 this.arrayToStoreChunks = []; // resetting array 1350 } 1351 } 1352 } 1353 1354 1355 /** 1356 * Adds a Listener to the codec that will handle all the messages to the application. 1357 * 1358 * @param {Listener} - A Listener function that should be defined in the application. 1359 * 1360 */ 1361 1362 Codec.prototype.addListener = function( listener ){ 1363 1364 this.listeners.push(listener); 1365 1366 } 1367 1368 /** 1369 * Removes a Listener to the codec that will handle all the messages to the application. 1370 * 1371 * @param {Listener} - The listener that should be removed. 1372 * 1373 */ 1374 1375 Codec.prototype.removeListener = function( listener ){ 1376 1377 var index = 0; 1378 if(this.listeners.indexOf(listener, index) !== -1){ 1379 index = this.listeners.indexOf(listener, index); 1380 this.listeners.splice(index, 1); 1381 } 1382 1383 1384 } 1385 1386 1387 /** 1388 * Adds a DataBroker to the codec. 1389 * 1390 * @param {dataBroker} - A DataBroker object. 1391 * 1392 */ 1393 1394 Codec.prototype.setDataBroker = function( dataBroker ){ 1395 1396 this.dataBroker = dataBroker; 1397 1398 } 1399 1400 /** 1401 * Store the received files in the harddisk. 1402 * 1403 * @param {fileUrl} - File URL where the applicationr receives the information about the file. 1404 * @param {fileName} - String which should contain the name to the file to store in the disk. 1405 * 1406 */ 1407 1408 Codec.prototype.saveToDisk= function(fileUrl, fileName) { 1409 var save = document.createElement('a'); 1410 save.href = fileUrl; 1411 save.target = '_blank'; 1412 save.download = fileName || fileUrl; 1413 var evt = document.createEvent('MouseEvents'); 1414 evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); 1415 1416 save.dispatchEvent(evt); 1417 1418 (window.URL || window.webkitURL).revokeObjectURL(save.href); 1419 } 1420 // ------------------------------- 1421 // DataCodec.js 1422 // ------------------------------- 1423 1424 /** 1425 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1426 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1427 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1428 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1429 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1430 * @author Kay Haensge <Kay.Haensge@telekom.de> 1431 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1432 */ 1433 1434 /** 1435 * Class DataMessage 1436 * creates all the json associated to the codecs messages 1437 * @class 1438 */ 1439 1440 function DataMessage (codecId, to, body){ 1441 1442 1443 this.codecId = codecId; 1444 this.to = to; //in case empty it sends the message to all clients 1445 this.body = body; 1446 } 1447 // ------------------------------- 1448 // DataMessage.js 1449 // ------------------------------- 1450 1451 /** 1452 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1453 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1454 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1455 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1456 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1457 * @author Kay Haensge <Kay.Haensge@telekom.de> 1458 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1459 */ 1460 1461 /** 1462 * Class DataMessage 1463 * creates all the json associated to the codecs messages 1464 * @class 1465 */ 1466 1467 function DataMessage (codecId, to, from, body){ 1468 1469 1470 this.codecId = codecId; 1471 this.to = to; //in case empty it sends the message to all clients 1472 this.body = body; 1473 this.from = from; 1474 } 1475 // ------------------------------- 1476 // DataBroker.js 1477 // ------------------------------- 1478 1479 /** 1480 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1481 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1482 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1483 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1484 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1485 * @author Kay Haensge <Kay.Haensge@telekom.de> 1486 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1487 */ 1488 1489 /** 1490 * @class 1491 * The DataBroker Class handles all the operations to choose the right codecs 1492 * and the channels to send, receive and handle messages related to the Data. 1493 * 1494 */ 1495 function DataBroker(){ 1496 1497 this.codecs = []; 1498 this.channels = []; 1499 } 1500 1501 /** 1502 * Receives a Message from channel that is designated to this codec to present it in the application. 1503 * 1504 * @param {JsonObject} - JsonObject with the content of a message, is a DataMessage Type. 1505 * 1506 */ 1507 DataBroker.prototype.onDataChannelEvt = function( msg ){ 1508 1509 var that = this; 1510 console.log(msg); 1511 1512 for(var i = 0; i < this.channels.length; i++){ 1513 this.channels[i].channel.onmessage = function(msg){ 1514 var msgObject = JSON.parse(msg.data); 1515 console.log("MESSAGE: ", msg); 1516 for(var i = 0; i < that.codecs.length; i++){ 1517 console.log("that.codecs[i].id: ", that.codecs[i].id); 1518 console.log("msgObject.codecId: ", msgObject.codecId); 1519 if( that.codecs[i].id == msgObject.codecId ){ 1520 console.log("that.codecs[i], ", that.codecs[i]); 1521 that.codecs[i].onData(msgObject); 1522 break; 1523 } 1524 1525 } 1526 } 1527 } 1528 1529 } 1530 1531 /** 1532 * Adds a Codec to the DataBroker. 1533 * 1534 * @param {Codec} - A Codec object. 1535 * 1536 */ 1537 1538 DataBroker.prototype.addCodec = function(codec){ 1539 codec.dataBroker=this; 1540 this.codecs.push(codec); 1541 1542 } 1543 1544 /** 1545 * Removes a Codec from the DataBroker. 1546 * 1547 * @param {Codec} - A Codec object. 1548 * 1549 */ 1550 DataBroker.prototype.removeCodec = function(codec){ 1551 1552 1553 //removecodec 1554 1555 } 1556 1557 /** 1558 * Adds a DataChannel to the Codec with the respective identity 1559 * 1560 * @param {DataChannel} - A DataChannel object. 1561 * @param {Identity} - An Identity object. 1562 * 1563 */ 1564 1565 DataBroker.prototype.addDataChannel = function(dataChannel, identity){ 1566 1567 var channel = { 1568 "identity": identity, 1569 "channel": dataChannel 1570 }; 1571 this.channels.push(channel); 1572 1573 } 1574 1575 /** 1576 * Removes DataChannel from the codec with the respective identity 1577 * 1578 * @param {Identity} - An Identity object. 1579 * 1580 */ 1581 1582 DataBroker.prototype.removeDataChannel = function(identity){ 1583 this.channels.forEach(function(element, index, array){ 1584 if(element.identity==identity) array.splice(index,1); 1585 }); 1586 1587 } 1588 1589 /** 1590 * Sends a Message from channel in the DataBroker. 1591 * 1592 * @param {String} - String with the content of a message 1593 * 1594 */ 1595 1596 DataBroker.prototype.send = function( msg ){ 1597 1598 console.log("MENSAGEM: ", msg); 1599 var index = -1; 1600 var msgObject = JSON.parse(msg); 1601 1602 if( msgObject.to == "" || typeof msgObject.to === 'undefined' ){ 1603 for(var i = 0; i < this.channels.length; i++){ 1604 console.log("channels"); 1605 this.channels[i].channel.send(msg); 1606 } 1607 1608 } 1609 else { 1610 for(var i = 0; i < this.channels.length; i++){ 1611 if( this.channels[i].identity.rtcIdentity === msgObject.to ) 1612 index = i; 1613 } 1614 if(index !== -1) 1615 this.channels[index].channel.send(msg); 1616 } 1617 1618 console.log(this.channels); 1619 console.log(this.codecs); 1620 1621 }// wonder_conversation.js 1622 //----------------------- 1623 1624 1625 // ------------------------------- 1626 // Resource.js 1627 // ------------------------------- 1628 1629 /** 1630 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1631 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1632 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1633 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1634 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1635 * @author Kay Haensge <Kay.Haensge@telekom.de> 1636 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1637 */ 1638 1639 1640 /** 1641 * @class 1642 * The Resource class represents the digital assets that are shared among participants in the conversation. 1643 * 1644 * @param {ResourceConstraint} resourceConstraint - Constraints of the Resource. Object with the following syntax {type: ResourceType, constraints: codec or MediaStreamConstraints} 1645 * @param {Codec} [codec] - For data types only, Codec used. 1646 * 1647 */ 1648 1649 function Resource( resourceConstraint, codec ) { 1650 1651 this.constraint = resourceConstraint; 1652 this.connections = new Array(); 1653 this.owner; 1654 1655 if(codec) 1656 { 1657 this.codec = codec; 1658 this.constraint.constraints=codec; 1659 } 1660 } 1661 1662 1663 /** 1664 * Resource class 1665 */ 1666 /*function Resource() { 1667 this.id; // We add a resource ID to identify it. 1668 this.type; // type of resource. 1669 this.stream; // StreamTrack (http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediastreamtrack) for this communication. 1670 this.data; // dataChannel for communication. 1671 this.evtHandler; // eventHandler. 1672 this.status; // status this resource. 1673 this.owner; // owner this resource/stream. 1674 //this.codecs; <- still no discuss 1675 var thisresource = this; 1676 // Question: how to handle the creation of PeerConnections and add local MediaStreams? do we need them stored at each Resource having Tracks from this stream?? 1677 // TODO: add stream attribute and rename "stream" to "streamTrack" 1678 1679 /** 1680 * private method setStream 1681 * 1682 * @param stream.. 1683 */ 1684 /* setStream = function(stream){ 1685 thisresource.stream = stream; 1686 } 1687 1688 /** 1689 * private method setData 1690 * 1691 * @param data 1692 */ 1693 /* setData = function(data){ 1694 thisresource.data = data; 1695 }; 1696 1697 /** 1698 * private method setStatus 1699 * 1700 * @param status ... sets the status attribute for a resource 1701 */ 1702 /* this.setStatus = function(status){ 1703 // TODO: ensure the transition in the state machine is allowed otherwise callback error 1704 switch(thisresource.status){ 1705 case ResourceStatus.RECORDING: 1706 if(status != ResourceStatus.NOT_SHARED || status != ResourceStatus.ENDED || status != ResourceStatus.PLAYING){ 1707 console.log("error setStatus" + status); 1708 break; 1709 } 1710 thisresource.status = status; 1711 break; 1712 case ResourceStatus.NOT_SHARED: 1713 if(status != ResourceStatus.SHARED || status != ResourceStatus.PLAYING || status != ResourceStatus.ENDED){ 1714 console.log("error setStatus" + status); 1715 break; 1716 } 1717 thisresource.status = status; 1718 break; 1719 case ResourceStatus.PAUSED: 1720 if(status != ResourceStatus.STOPPED){ 1721 console.log("error setStatus" + status); 1722 break; 1723 } 1724 thisresource.status = status 1725 break 1726 case ResourceStatus.PLAYING: 1727 if(status != ResourceStatus.PAUSED || status != ResourceStatus.STOPPED || status != ResourceStatus.SHARED || status != ResourceStatus.NOT_SHARED){ 1728 console.log("error setStatus" + status); 1729 break; 1730 } 1731 thisresource.status = status; 1732 break; 1733 case ResourceStatus.SHARED: 1734 if(status != ResourceStatus.NOT_SHARED || status != ResourceStatus.RECORDING || status != ResourceStatus.PLAYING || status != ResourceStatus.ENDED){ 1735 console.log("error setStatus" + status); 1736 break; 1737 } 1738 thisresource.status = status; 1739 break; 1740 case ResourceStatus.STOPPED: 1741 console.log("transition is not allowed"); 1742 break; 1743 case ResourceStatus.ENDED: 1744 if(status != ResourceStatus.PLAYING){ 1745 console.log("error setStatus" + status); 1746 break; 1747 } 1748 thisresource.status = status; 1749 break; 1750 case ResourceStatus.LIVE: 1751 if(status != ResourceStatus.SHARED || status != ResourceStatus.NOT_SHARED){ 1752 console.log("error setStatus" + status); 1753 break; 1754 } 1755 thisresource.status = status; 1756 break; 1757 case ResourceStatus.NEW: 1758 if(status != ResourceStatus.LIVE){ 1759 console.log("error setStatus" + status); 1760 break; 1761 } 1762 thisresource.status = status; 1763 break; 1764 default: 1765 thisresource.status = status; 1766 break; 1767 } 1768 1769 } 1770 }; 1771 1772 /** 1773 * createStream 1774 * 1775 * @param owner : Participant ... owner of the stream 1776 * @param stream : MediaStream ... the stream 1777 * @param type : ResourceType... type of resource 1778 */ 1779 /*Resource.prototype.createStream = function(owner,stream,type) { 1780 if(! stream || ! owner) return ; 1781 else{ 1782 this.owner=owner; 1783 this.stream=stream; 1784 this.type = type; 1785 this.id = guid(); 1786 this.setStatus(ResourceStatus.NEW); 1787 } 1788 1789 // TODO Where/how to assign the type,data,connection,evtHandler and status? 1790 1791 }; 1792 1793 1794 /** 1795 * getStatus 1796 * 1797 * @returns ResourceStatus ... returns the resource status 1798 */ 1799 /*Resource.prototype.getStatus = function(){ 1800 return this.status; 1801 }; 1802 1803 1804 /** 1805 * destroy 1806 * 1807 */ 1808 /*Resource.prototype.stop = function(){ 1809 // @pchainho TODO: only applicable for local Resources. 1810 1811 this.stream = null; 1812 //this.setStatus(ResourceStatus.ENDED); // @pchainho I guess this should only be done when "ended" event is fired. To study how to address Data Channel resources 1813 1814 // TODO: depending on the type of the Resource it may imply the invocation of browser APIs eg for MediaStreamTracks call its operation stop() 1815 }; 1816 1817 /** 1818 * share 1819 * 1820 * @param shared : Boolean ... establishes if the resource is shared or not 1821 * 1822 */ 1823 /*Resource.prototype.share = function(shared){ 1824 // @pchainho TODO: only applicable for local Resources. For MediaStreamTracks call its operation stop() 1825 1826 if(shared) this.setStatus( ResourceStatus.SHARED ); 1827 else this.setStatus( ResourceStatus.NOT_SHARED); 1828 // TODO: depending on the type of the Resource it may imply the invocation of browser APIs 1829 }; 1830 1831 /** 1832 * record 1833 * 1834 */ 1835 /*Resource.prototype.record = function(){ 1836 // TODO Complete the function 1837 }; 1838 1839 /** 1840 * play 1841 * 1842 * @param timing : Number ... the time to start playing 1843 * 1844 */ 1845 /*Resource.prototype.play = function(timing){ 1846 // this.status = ResourceStatus.PLAYING; 1847 // TODO What to do with the timing ? How to get it playing ? 1848 }; 1849 1850 /** 1851 * pause 1852 * 1853 */ 1854 /*Resource.prototype.pause = function(){ 1855 // this.status = ResourceStatus.PAUSED; 1856 }; 1857 1858 /** 1859 * create 1860 * 1861 * @param owner : Participant ... specifies the owner of the resource 1862 * @param type : ResourceType ... specifies the resource type 1863 * 1864 */ 1865 /*Resource.prototype.create = function(owner, type){ 1866 if(! owner || ! type)return ; 1867 else{ 1868 this.owner=owner; 1869 this.type=type; 1870 this.id=guid(); // unique uid for resource 1871 this.setStatus(ResourceStatus.NEW); 1872 } 1873 // TODO Where/how to assign the stream,data,connection,evtHandler and status? 1874 }; 1875 1876 /** 1877 * createData 1878 * 1879 * @param owner : Participant ... specifies the owner of the resource 1880 * @param type : ResourceType ... specifies the resource type 1881 * @param data : DataChannel ... specifies the data channel 1882 * 1883 */ 1884 /*Resource.prototype.createData = function(owner, type, data){ 1885 1886 if(! owner || ! type || ! data ) return ; 1887 else { 1888 this.owner=owner; 1889 this.type=type; 1890 this.data=data; 1891 } 1892 // TODO Where/how to assign the stream,connection,evtHandler and status? 1893 }; 1894 1895 /** 1896 * EventHandler 1897 * 1898 * @param that 1899 */ 1900 /*stream.started = function(self){ 1901 1902 } 1903 1904 /** 1905 * EventHandler 1906 * 1907 * @param that 1908 *//* 1909 function mute(self){ 1910 1911 } 1912 1913 /** 1914 * EventHandler 1915 * 1916 * @param that 1917 *//* 1918 function unmute(self){ 1919 1920 } 1921 1922 /** 1923 * EventHandler 1924 * 1925 * @param that 1926 *//* 1927 function overconstrained(self){ 1928 1929 } 1930 1931 /** 1932 * EventHandler 1933 * 1934 * @param that 1935 */ 1936 /* 1937 function ended(that){ 1938 1939 }*/ 1940 // ------------------------------- 1941 // Participant.js 1942 // ------------------------------- 1943 1944 /** 1945 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 1946 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 1947 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 1948 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 1949 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 1950 * @author Kay Haensge <Kay.Haensge@telekom.de> 1951 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 1952 */ 1953 1954 /** 1955 * @class 1956 * The Participant class handles all operations needed to manage the participation of an 1957 * Identity (User) in a conversation including the WebRTC PeerConnection functionalities. 1958 * The Local Participant is associated with the Identity that is using the Browser while the 1959 * Remote Participant is associated to remote Identities (users) involved in the conversation. 1960 * 1961 */ 1962 function Participant() { 1963 1964 this.identity = ""; // Identity of the participant 1965 this.RTCPeerConnection = ""; // RTCPeerConnection for that participant 1966 this.status = ""; // Status 1967 this.me = ""; // Participant representing the user of the browser. 1968 this.rtcEvtHandler = ""; // Event handler for WebRTC events 1969 this.msgHandler = ""; // Event handler for signalling events 1970 this.contextId = ""; // Context ID of the conversation the participant belongs to. 1971 this.resources = new Array(); // Resources of the participant 1972 this.hosting = ""; // Hosting participant of the conversation this participant belongs to. 1973 this.connectedIdentities = new Array(); // For multiparty, array of Identities of the connected peers. 1974 this.dataBroker; // DataBroker for WebRTC DataChannel. 1975 1976 /********************************* 1977 * PRIVATE METHODS * 1978 * TODO: try to have them accessible from other Wonder classes eg Conversation and MessagingSub but not visible from the App */ 1979 /*********************************/ 1980 1981 /** 1982 * @ignore 1983 */ 1984 var thisParticipant = this; 1985 setStatus = function(status) { 1986 1987 //verify if all of the previous status allows to the participant to pass to the next status 1988 //if it is possible just setStatus, else return a message error. 1989 // (@ pchainho) check if the transition is allowed by the state machine. 1990 1991 switch (thisParticipant.status) { 1992 case ParticipantStatus.CREATED: 1993 if (status != ParticipantStatus.WAITING && status != ParticipantStatus.PENDING) { 1994 return false; 1995 } 1996 else { 1997 thisParticipant.status = status; 1998 console.log(thisParticipant); 1999 return true; 2000 2001 } 2002 break; 2003 case ParticipantStatus.ACCEPTED: 2004 if (status != ParticipantStatus.PARTICIPATING && status != ParticipantStatus.FAILED) { 2005 return false; 2006 } 2007 else { 2008 thisParticipant.status = status; 2009 console.log(thisParticipant); 2010 return true; 2011 } 2012 break; 2013 case ParticipantStatus.PARTICIPATING: 2014 if (status != ParticipantStatus.PARTICIPATED && status != ParticipantStatus.NOT_PARTICIPATING) { 2015 return false; 2016 } 2017 else { 2018 thisParticipant.status = status; 2019 console.log(thisParticipant); 2020 return true; 2021 } 2022 break; 2023 case ParticipantStatus.PENDING: 2024 if (status != ParticipantStatus.ACCEPTED && status != ParticipantStatus.MISSED) { 2025 return false; 2026 } 2027 else { 2028 thisParticipant.status = status; 2029 console.log(thisParticipant); 2030 return true; 2031 } 2032 break; 2033 case ParticipantStatus.FAILED: 2034 console.log("transition is not allowed"); 2035 return false; 2036 break; 2037 case ParticipantStatus.MISSED: 2038 console.log("transition is not allowed"); 2039 return false; 2040 break; 2041 case ParticipantStatus.PARTICIPATED: 2042 thisParticipant.status = status; 2043 console.log(thisParticipant); 2044 return true; 2045 break; 2046 case ParticipantStatus.WAITING: 2047 if (status != ParticipantStatus.PARTICIPATING && status != ParticipantStatus.FAILED) { 2048 return false; 2049 } 2050 else { 2051 thisParticipant.status = status; 2052 console.log(thisParticipant); 2053 return true; 2054 } 2055 break; 2056 case ParticipantStatus.NOT_PARTICIPATING: 2057 thisParticipant.status = status; 2058 console.log(thisParticipant); 2059 return true; 2060 2061 break; 2062 default: 2063 thisParticipant.status = status; 2064 console.log(thisParticipant); 2065 return true; 2066 break; 2067 } 2068 }; 2069 } 2070 ; 2071 2072 /** 2073 * Creates the local participant and initializes its resources. 2074 * 2075 * @param {Identity} identity - {@link Identity} of the participant 2076 * @param {ResourceConstraints[]} resourceConstraints - Array of constraints for the initial resources of the local participant. (CURRENT IMPLEMENTATION WILL TAKE THE FIRST ONE) 2077 * @param {onRTCEvt} rtcEvtHandler - Callback function that handles WebRTC Events. 2078 * @param {onMessage} msgHandler - Callback function that handles signaling Events. 2079 * @param {callback} callback - Callback function for success creation of the local participant. 2080 * @param {errorCallback} errorCallback - Callback function for errors in the creation of the local participant. 2081 * 2082 */ 2083 2084 Participant.prototype.createMyself = function(identity, resourceConstraints, rtcEvtHandler, msgHandler, callback, errorCallback) { 2085 2086 this.identity = identity; 2087 this.me = this; 2088 this.rtcEvtHandler = rtcEvtHandler; 2089 this.msgHandler = msgHandler; 2090 2091 setStatus(ParticipantStatus.CREATED); // @pchainho TODO: to catch errors 2092 2093 2094 var doGetUserMedia = false; 2095 var doDataChannel = false; 2096 var conversationResource = false; 2097 var constraints = new Object(); 2098 constraints.audio = false; 2099 constraints.video = false; 2100 2101 2102 // Create RTCPeerConnection 2103 try { 2104 // Create an RTCPeerConnection via the polyfill (adapter.js). 2105 var pc = new RTCPeerConnection({'iceServers': new Array()}); 2106 console.log('Created RTCPeerConnnection.'); 2107 } 2108 catch (e) { 2109 console.log('Failed to create PeerConnection, exception: ' + e.message); 2110 alert('Cannot create RTCPeerConnection object; \ 2111 WebRTC is not supported by this browser.'); 2112 return; 2113 } 2114 2115 this.setRTCPeerConnection(pc); 2116 2117 2118 2119 2120 // TODO: Solve the problem where for many resourceConstraints, we would have a loop with callbacks inside. 2121 // Process the constraints, ordering them by media ones and data ones (maybe in 2 arrays). Merge if necessary 2122 //resourceConstraints=resourceConstraints[0]; // <dirtyFix> 2123 var thatIdentity = this.identity; 2124 var thatResources = this.resources; 2125 var thatDataBroker = this.dataBroker; 2126 var that = this; 2127 var numbResource = 0; 2128 var flag = false; 2129 var dataBroker; 2130 var one = false; 2131 var getMedia = function(numbResource,recursiveCallback){ 2132 if(resourceConstraints.length > numbResource){ 2133 2134 if (doGetUserMedia === true && resourceConstraints[numbResource].direction!="in" && resourceConstraints[numbResource].type == "audioVideo") { 2135 var thisParticipant = that; 2136 flag = true; 2137 // TODO: Merge the media constraints so there is only 1 getUserMedia call (or maybe 2 if screensharing) 2138 // Loop/cascade callback so all the media is added and resources created. 2139 getUserMedia(constraints, function (stream) { 2140 var evt=new Object(); 2141 evt.stream=stream; 2142 thisParticipant.rtcEvtHandler('onaddlocalstream',evt); 2143 console.log(numbResource); 2144 2145 pc.addStream(stream); 2146 var resource = new Resource(resourceConstraints[numbResource -1]); 2147 resource.id=stream.id; 2148 //resource.constraint = resourceConstraints[numbResource]; 2149 console.log( resourceConstraints[numbResource]); 2150 resource.constraint.constraints = {id: stream.id}; 2151 resource.owner = that.identity; 2152 resource.connections.push(pc); 2153 thisParticipant.resources.push(resource); 2154 2155 callback(); 2156 }, errorCallback); 2157 // return; 2158 } 2159 if (doDataChannel === true && resourceConstraints[numbResource].direction!="in" && (resourceConstraints[numbResource].type != "audioVideo") ) { 2160 // Loop so all the codecs are created and initialized. 2161 var numb = numbResource; 2162 var sucess = callback; 2163 var creatResources = function(recursive,numb,sucess){ 2164 if(resourceConstraints.length>numb){ 2165 var codec = new Codec(resourceConstraints[numb].type); 2166 //codec.id=resourceConstraints[numb].id; 2167 console.log(codec); 2168 if(resourceConstraints[numb].id) codec.id = resourceConstraints[numb].id; 2169 var resource = new Resource(resourceConstraints[numb], codec); 2170 console.log(resource); 2171 resource.connections.push(pc); 2172 resource.owner = thatIdentity; 2173 thatResources.push(resource); 2174 console.log(thatResources) 2175 var evt = new Object(); 2176 evt.codec = codec; 2177 that.onRTCEvt('onResourceParticipantAddedEvt', evt); 2178 2179 numb++; 2180 recursive(recursive,numb,sucess); 2181 }else{ 2182 // sucess() 2183 } 2184 } 2185 creatResources(creatResources,numb,sucess); 2186 2187 } 2188 numbResource++; 2189 recursiveCallback(numbResource,recursiveCallback); 2190 }else{ 2191 if(flag != true ){ 2192 callback(); 2193 } 2194 } 2195 } 2196 var iteration=0; 2197 var creatConstraints = function(iteration, callbackRecursive){ 2198 if(resourceConstraints.length>iteration){ 2199 switch (resourceConstraints[iteration].type) { 2200 2201 case ResourceType.AUDIO_MIC: 2202 constraints.audio = true; 2203 doGetUserMedia = true; 2204 break; 2205 case ResourceType.VIDEO_CAM: 2206 constraints.video = true; 2207 doGetUserMedia = true; 2208 break; 2209 case ResourceType.AUDIO_VIDEO: 2210 constraints.video = true; 2211 constraints.audio = true; 2212 doGetUserMedia = true; 2213 break; 2214 case ResourceType.SCREEN: 2215 constraints.video = { 2216 mandatory: { 2217 chromeMediaSource: 'screen', 2218 maxWidth: 1280, 2219 maxHeight: 720 2220 }, 2221 optional: [] 2222 }; 2223 constraints.audio = false; 2224 doGetUserMedia = true; 2225 break; 2226 case ResourceType.FILE: 2227 doDataChannel = true; 2228 break; 2229 case ResourceType.CHAT: 2230 doDataChannel = true; 2231 break; 2232 } 2233 iteration++; 2234 callbackRecursive(iteration,callbackRecursive); 2235 }else{ 2236 getMedia(numbResource,getMedia); 2237 } 2238 2239 } 2240 2241 creatConstraints(iteration,creatConstraints); 2242 }; 2243 2244 2245 2246 2247 /** 2248 * Creates a remote participant 2249 * 2250 * @param {Identity} identity - {@link Identity} of the participant 2251 * @param {Participant} myParticipant - {@link Participant} representing the local user of the application. 2252 * @param {string} contextId - Identifier of the conversation this participant belongs to. 2253 * @param {ResourceConstraints[]} resourceConstraints - Array of constraints for the initial resources of the remote participant (CURRENT IMPLEMENTATION WILL TAKE THE FIRST ONE). 2254 * @param {onRTCEvt} rtcEvtHandler - Callback function that handles WebRTC Events. 2255 * @param {onMessage} msgHandler - Callback function that handles signaling Events. 2256 * @param {RTCIceServer} iceServers - Configuration parameters for ICE servers. {@link http://www.w3.org/TR/webrtc/#widl-RTCConfiguration-iceServers} 2257 * 2258 */ 2259 2260 Participant.prototype.createRemotePeer = function(identity, myParticipant, contextId, resourceConstraints, rtcEvtHandler, msgHandler, iceServers) { 2261 // # if we follow the state diagram, it will be needed to set the status to created 2262 setStatus(ParticipantStatus.CREATED); 2263 this.identity = identity; 2264 this.me = myParticipant; 2265 this.rtcEvtHandler = rtcEvtHandler; 2266 this.msgHandler = msgHandler; 2267 this.contextId = contextId; 2268 var channel; 2269 var thisParticipant = this; 2270 var oneDataChannel = false; 2271 constraints = resourceConstraints; 2272 var ite = 0; 2273 var that = this; 2274 //media and Data 2275 var getMedia = function(ite,functionCallback,pc,oneDataChannel){ 2276 if(constraints.length > ite){ 2277 var type; 2278 if(that.me.getResources(constraints[ite])[0] == undefined){ 2279 type == null; 2280 } 2281 else{ 2282 type = that.me.getResources(constraints[ite])[0].constraint.type; 2283 } 2284 // create data channel and setup chat 2285 if(data && constraints[ite].direction!="in" && (type == "chat" || type == "file" )){ 2286 console.log("Creating remote peer with local constraints: ", constraints); 2287 2288 if(oneDataChannel == false){ 2289 oneDataChannel = true; 2290 channel = pc.createDataChannel("dataChannel"); 2291 2292 //thisParticipant.setDataBroker(constraints.constraints.dataBroker); 2293 thisParticipant.dataBroker.addDataChannel(channel, thisParticipant.identity); 2294 channel.onopen = function () { 2295 thisParticipant.dataBroker.onDataChannelEvt(); 2296 //thisParticipant.onRTCEvt("onResourceParticipantAddedEvt", resourceData); 2297 } 2298 2299 // setup chat on incoming data channel 2300 pc.ondatachannel = function (evt) { 2301 channel = evt.channel; 2302 }; 2303 var resourceData; 2304 } 2305 resourceData = that.me.getResources(constraints[ite])[0]; 2306 resourceData.connections.push(pc); 2307 } 2308 2309 if(media && constraints[ite].direction!="in" && (type =="audioVideo" )){ 2310 var resourceMedia = that.me.getResources(constraints[ite])[0]; 2311 var stream = that.me.RTCPeerConnection.getStreamById(resourceMedia.constraint.constraints.id); 2312 pc.addStream(stream); 2313 resourceMedia.connections.push(pc); 2314 } 2315 2316 if (constraints[ite].direction != "out") { 2317 if (media === true) { 2318 var resource = new Resource(resourceConstraints); 2319 resource.owner = that.identity; 2320 resource.connections.push(pc); 2321 thisParticipant.resources.push(resource); 2322 } 2323 if (data === true) { 2324 var resource = new Resource(resourceConstraints); 2325 //var codec = new Codec(resourceConstraints.type); 2326 //resource.codec = codec; 2327 resource.owner = that.identity; 2328 resource.connections.push(pc); 2329 thisParticipant.resources.push(resource); 2330 } 2331 } 2332 ite++; 2333 that.setRTCPeerConnection(pc); 2334 functionCallback(ite,functionCallback,pc,oneDataChannel); 2335 }else{ 2336 // Create RTCPeerConnection 2337 2338 } 2339 } 2340 2341 2342 // end Media and Data 2343 var media = false; 2344 var data = false; 2345 var iteration = 0; 2346 var creatResource = function(iteration,callbackFunction){ 2347 if(constraints.length > iteration){ 2348 switch (constraints[iteration].type) { 2349 case ResourceType.AUDIO_MIC: 2350 media = true; 2351 break; 2352 case ResourceType.VIDEO_CAM: 2353 media = true; 2354 break; 2355 case ResourceType.AUDIO_VIDEO: 2356 media = true; 2357 break; 2358 case ResourceType.SCREEN: 2359 media = true; 2360 break; 2361 case ResourceType.FILE: 2362 data = true; 2363 break; 2364 case ResourceType.CHAT: 2365 data = true; 2366 break; 2367 } 2368 iteration++; 2369 callbackFunction(iteration,callbackFunction); 2370 }else{ 2371 try { 2372 // Create an RTCPeerConnection via the polyfill (adapter.js). 2373 var mediaConstraints = {optional: [{RtpDataChannels: true}]}; 2374 if(!iceServers) iceServers = {'iceServers': new Array()}; 2375 console.log('Creating RTCPeerConnnection with:\n' + ' config: \'' + JSON.stringify(iceServers) + '\';\n' + ' constraints: \'' + JSON.stringify(mediaConstraints) + '\'.'); 2376 2377 var pc = new RTCPeerConnection(iceServers, mediaConstraints); 2378 2379 2380 } 2381 catch (e) { 2382 console.log('Failed to create PeerConnection, exception: ' + e.message); 2383 alert('Cannot create RTCPeerConnection object; \ 2384 WebRTC is not supported by this browser.'); 2385 return; 2386 } 2387 getMedia(ite,getMedia,pc,oneDataChannel); 2388 } 2389 } 2390 creatResource(iteration,creatResource); 2391 } 2392 2393 2394 2395 2396 2397 2398 /**@ignore 2399 * setRTCPeerConnection 2400 * 2401 * @param RTCPeerConnection : PeerConnection ... sets the connection attribute for a participant 2402 */ 2403 Participant.prototype.setRTCPeerConnection = function(RTCPeerConnection) { 2404 var thisParticipant = this; 2405 this.RTCPeerConnection = RTCPeerConnection; 2406 2407 /** 2408 * onsignalingstatechange 2409 * 2410 * It is called any time the readyState changes 2411 */ 2412 this.RTCPeerConnection.onsignalingstatechange = function(evt) { 2413 thisParticipant.onRTCEvt("onsignalingstatechange", evt); 2414 }; 2415 2416 /** 2417 * oniceconnectionstatechange 2418 * 2419 * It is called any time the iceConnectionState changes. 2420 */ 2421 this.RTCPeerConnection.oniceconnectionstatechange = function(evt) { 2422 thisParticipant.onRTCEvt("oniceconnectionstatechange", evt); 2423 }; 2424 2425 /** 2426 * onaddstream 2427 * 2428 * It is called any time a MediaStream is added by the remote peer 2429 */ 2430 2431 this.RTCPeerConnection.onaddstream = function(evt) { 2432 thisParticipant.onRTCEvt("onaddstream", evt); 2433 }; 2434 2435 /** 2436 * onicecandidate 2437 * 2438 */ 2439 2440 this.RTCPeerConnection.onicecandidate = function(evt) { 2441 thisParticipant.onRTCEvt("onicecandidate", evt); 2442 }; 2443 2444 /** 2445 * onnegotiationneeded 2446 * 2447 */ 2448 2449 this.RTCPeerConnection.onnegotiationneeded = function(evt) { 2450 thisParticipant.onRTCEvt("onnegotiationneeded", evt); 2451 }; 2452 2453 /** 2454 * ondatachannel 2455 * 2456 */ 2457 2458 this.RTCPeerConnection.ondatachannel = function(evt) { 2459 thisParticipant.onRTCEvt("ondatachannel", evt); 2460 }; 2461 2462 /** 2463 * onremovestream 2464 * 2465 * It is called any time a MediaStream is removed by the remote peer. 2466 */ 2467 2468 this.RTCPeerConnection.onremovestream = function(evt) { 2469 thisParticipant.onRTCEvt("onremovestream", evt); 2470 }; 2471 2472 2473 }; 2474 2475 2476 /** 2477 * Returns a reference to the RTCPeerConnection that is established with this participant. 2478 * 2479 * @returns PeerConnection ... gets the connection attribute for a participant 2480 * 2481 */ 2482 Participant.prototype.getRTCPeerConnection = function() { 2483 return this.RTCPeerConnection; 2484 }; 2485 2486 2487 2488 /** 2489 * This callback type is called `onRTCEvt` and handles the WebRTC events from the RTCPeerConnection. 2490 * 2491 * @callback onRTCEvt 2492 * @param {event} event - Event from {@link http://www.w3.org/TR/webrtc/#event-summary} + 'onResourceParticipantAddedEvt' + 'onaddlocalstream'. 2493 * @param {evt} evt - Returned stream, candidate, etc. see {@link http://www.w3.org/TR/webrtc/#event-summary} 2494 */ 2495 Participant.prototype.onRTCEvt = function(event, evt) { 2496 // TODO To implement and pass the events up 2497 switch (event) { 2498 2499 case 'onnegotiationneeded': 2500 this.rtcEvtHandler(event, evt); 2501 break; 2502 case 'onicecandidate': 2503 if (evt.candidate) { 2504 var message = new MessageFactory.createCandidateMessage("","","",evt.candidate.sdpMLineIndex,evt.candidate.sdpMid,evt.candidate.candidate,false); 2505 } else { 2506 var message = new MessageFactory.createCandidateMessage("","","","","",this.RTCPeerConnection.localDescription,true); 2507 console.log("End of Ice Candidates"); 2508 } 2509 this.sendMessage(message.body,MessageType.CONNECTIVITY_CANDIDATE,"",function(){},function(){}); 2510 this.rtcEvtHandler(event, evt); 2511 break; 2512 case 'onsignalingstatechange': 2513 this.rtcEvtHandler(event, evt); 2514 break; 2515 case 'onaddstream': 2516 console.log("stream added"); 2517 this.rtcEvtHandler(event, evt); 2518 break; 2519 case 'onremovestream': 2520 this.rtcEvtHandler(event, evt); 2521 break; 2522 case 'oniceconnectionstatechange': 2523 this.rtcEvtHandler(event, evt); 2524 break; 2525 case 'ondatachannel': 2526 this.rtcEvtHandler(event, evt); 2527 break; 2528 default: 2529 this.rtcEvtHandler(event, evt); 2530 break; 2531 2532 } 2533 } 2534 2535 /** 2536 * This callback type is called `onRTCEvt` and handles the WebRTC events from the RTCPeerConnection. 2537 * 2538 * @callback onMessage 2539 * @param {Message} message - {@link Message} received 2540 */ 2541 Participant.prototype.onMessage = function(message) { 2542 2543 switch (message.type) { 2544 2545 case MessageType.ACCEPTED: 2546 var mediaConstraints = message.body.constraints; 2547 var that = this; 2548 var exist = false; 2549 if(typeof message.body.connectionDescription !== 'undefined' && message.body.connectionDescription !== ""){ 2550 2551 console.log("Participant " + this.identity.rtcIdentity + " received accepted."); 2552 var description = new RTCSessionDescription(message.body.connectionDescription); 2553 this.RTCPeerConnection.setRemoteDescription(description, 2554 onSetSessionDescriptionSuccess, onSetSessionDescriptionError); 2555 console.log("Remote Description set: ", this); 2556 this.getResources(mediaConstraints)[0].constraint=mediaConstraints; 2557 2558 if(this.me.identity.rtcIdentity == this.hosting.rtcIdentity){ 2559 //see if the hosting is equal to this.me 2560 //send a accepted message with no SDP 2561 this.me.connectedIdentities.push(message.from.rtcIdentity); 2562 var answerBody = new Object(); 2563 answerBody.connected = this.me.connectedIdentities; 2564 answerBody.from = message.from; 2565 answerBody.to = ""; 2566 this.me.sendMessage(answerBody, MessageType.ACCEPTED, mediaConstraints); 2567 } 2568 }else{ 2569 if(message.body.connected.length != 0){ 2570 for(var i = 0; i < message.body.connected.length; i++){ 2571 //ignore the message if my rtcIdentity is in the this.connectedIdentities 2572 if(message.body.connected[i] == that.me.identity.rtcIdentity){ 2573 exist = true; 2574 break; 2575 } 2576 } 2577 if(!exist){ 2578 //if not send a message to the all of candidates 2579 that.sendMessage("", MessageType.INVITATION, mediaConstraints); 2580 } 2581 } 2582 } 2583 2584 2585 this.msgHandler(message); 2586 break; 2587 case MessageType.CONNECTIVITY_CANDIDATE: 2588 if (message.body.lastCandidate) { 2589 console.log("Participant " + this.identity.rtcIdentity + " reached End of Candidates."); 2590 } 2591 else 2592 { 2593 var candidate = new RTCIceCandidate({ 2594 sdpMLineIndex: message.body.label, 2595 sdpMid: message.body.id, 2596 candidate: message.body.candidateDescription 2597 }); 2598 console.log("Participant " + this.identity.rtcIdentity + " added a candidate:", candidate); 2599 this.RTCPeerConnection.addIceCandidate(candidate); 2600 } 2601 break; 2602 case MessageType.NOT_ACCEPTED: 2603 this.leave(false); 2604 console.log("Participant received NOT_ACCEPTED"); 2605 this.msgHandler(message); 2606 break; 2607 case MessageType.CANCEL: 2608 this.msgHandler(message); 2609 break; 2610 case MessageType.ADD_RESOURCE: 2611 this.msgHandler(message); 2612 break; 2613 case MessageType.UPDATE: 2614 this.msgHandler(message); 2615 break; 2616 case MessageType.UPDATED: 2617 var description = new RTCSessionDescription(message.body.newConnectionDescription); 2618 this.RTCPeerConnection.setRemoteDescription(description, 2619 onSetSessionDescriptionSuccess, onSetSessionDescriptionError); 2620 console.log("Remote Description set: ", description); 2621 this.getResources(mediaConstraints)[0].constraint=mediaConstraints; 2622 this.msgHandler(message); 2623 break; 2624 case MessageType.REDIRECT: 2625 this.msgHandler(message); 2626 break; 2627 case MessageType.BYE: 2628 this.leave(false); 2629 console.log("Participant received BYE"); 2630 this.msgHandler(message); 2631 break; 2632 case MessageType.OFFER_ROLE: 2633 this.msgHandler(message); 2634 break; 2635 case MessageType.INVITATION: 2636 // IF GETS HERE IT IS NORMAL FOR THE MULTIPARTY 2637 var mediaConstraints = message.body.constraints; 2638 var description = new RTCSessionDescription(message.body.connectionDescription); 2639 this.RTCPeerConnection.setRemoteDescription(description, onSetSessionDescriptionSuccess, onSetSessionDescriptionError); 2640 2641 this.sendMessage(answerBody, MessageType.ACCEPTED, mediaConstraints); 2642 //this.msgHandler(message); 2643 break; 2644 case MessageType.RESOURCE_REMOVED: 2645 this.msgHandler(message); 2646 break; 2647 case MessageType.REMOVE_PARTICIPANT: 2648 this.msgHandler(message); 2649 break; 2650 case MessageType.SHARE_RESOURCE: 2651 this.msgHandler(message); 2652 break; 2653 default: 2654 // forward to application level 2655 this.msgHandler(message); 2656 break; 2657 } 2658 } 2659 2660 2661 /** @ignore */ 2662 Participant.prototype.connectStub = function(callback) { 2663 var thisParticipant = this; 2664 thisParticipant.identity.resolve(function(stub) {// @pchainho: why is this needed? 2665 stub.addListener(thisParticipant.onMessage.bind(thisParticipant), thisParticipant.identity.rtcIdentity, thisParticipant.contextId); 2666 stub.connect(thisParticipant.me.identity.rtcIdentity,thisParticipant.me.identity.credentials,callback);// @pchainho: we are using here the credentials of other users?? 2667 }); 2668 } 2669 2670 2671 /** 2672 * 2673 * The method will create the message and send it to the participant. 2674 * 2675 * @param {MessageBody} messageBody - The body of the message (depends on the MessageType) 2676 * @param {MessageType} messageType - The type of the message. 2677 * @param {ResourceConstraints} [constraints] - For the messages that imply information about the Resources, contraints about them. 2678 * @param {callback} callback - Callback for successful sending. 2679 * @param {errorCallback} errorCallback - Error Callback 2680 * 2681 */ 2682 Participant.prototype.sendMessage = function(messageBody, messageType, constraints, callback, errorCallback) { 2683 // Sends the message 2684 2685 2686 sdpConstraints = {'mandatory': {'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true}}; 2687 2688 var message = new Message(); 2689 var thisParticipant = this; 2690 console.log(this.resources) 2691 console.log(constraints) 2692 var i; 2693 /* for(i = 0;i<constraints.length;i++){ 2694 if(constraints[i].type==ResourceType.CHAT || constraints[i].type==ResourceType.FILE) 2695 { 2696 var codec = new Codec(constraints[i].type, constraints[i].CodecLibUrl); 2697 codec.id = constraints[i].id; 2698 codec.description = constraints[i].description; 2699 codec.mime_type = constraints[i].mime_type; 2700 constraints[i] = codec; 2701 } 2702 }*/ 2703 2704 2705 switch(messageType){ 2706 case MessageType.INVITATION: 2707 console.log("MessageType.INVITATION: ", messageBody); 2708 console.log("constrains",constraints) 2709 // define new type of constraints because when isn't possible to send the DataBroker in message... 2710 var constraintsAux =new Array(); 2711 var i; 2712 var iteration; 2713 2714 for(iteration=0;iteration<constraints.length;iteration++){ 2715 var aux = new Object() 2716 if(constraints[iteration].constraints){ 2717 aux.id = constraints[iteration].constraints.id; 2718 }else{ 2719 aux.id = constraints[iteration].id; 2720 } 2721 aux.type = constraints[iteration].type; 2722 aux.direction = constraints[iteration].direction; 2723 constraintsAux.push(aux); 2724 } 2725 2726 console.log("invite") 2727 if (!messageBody) message = MessageFactory.createInvitationMessage(this.me.identity, this.identity, this.contextId, constraintsAux); 2728 else message = MessageFactory.createInvitationMessage(this.me.identity, this.identity, this.contextId, constraintsAux, messageBody.conversationURL, messageBody.subject, messageBody.hosting, messageBody.agenda, messageBody.peers); 2729 setStatus(ParticipantStatus.PENDING); // TODO: OR WAITING?? Check for errors. 2730 2731 this.RTCPeerConnection.createOffer(function (sessionDescription) { 2732 thisParticipant.RTCPeerConnection.setLocalDescription(sessionDescription, function () { 2733 console.log("Local description set: ", sessionDescription); 2734 message.body.connectionDescription = thisParticipant.RTCPeerConnection.localDescription; 2735 2736 console.log("Sending message with constraints: ", constraints); 2737 2738 if (!thisParticipant.identity.messagingStub){ 2739 errorCallback("Messaging Stub not well initialized"); 2740 errorCallback; 2741 } 2742 else thisParticipant.identity.messagingStub.sendMessage(message); 2743 2744 if (callback) 2745 callback(); 2746 2747 }, function(error){errorCallback(error)}); 2748 }, function(error){errorCallback(error)}, sdpConstraints); 2749 2750 break; 2751 case MessageType.ACCEPTED: 2752 var constraintsAux =new Array(); 2753 var i; 2754 for(i =0;i<constraints.length;i++){ 2755 var aux = new Object(); 2756 aux.id = constraints[i].id; 2757 aux.type = constraints[i].type; 2758 aux.direction = constraints[i].direction; 2759 constraintsAux.push(aux); 2760 } 2761 console.log(messageBody); 2762 console.log("this.me.identity.rtcIdentity: ", this.me.identity.rtcIdentity); 2763 console.log("this.hosting.rtcIdentity: ", this.hosting.rtcIdentity); 2764 //if(this.me.identity.rtcIdentity === thisParticipant.identity.rtcIdentity){ 2765 if(this.me.identity.rtcIdentity === this.hosting.rtcIdentity){ 2766 //send a accepted message with no SDP inside 2767 var message = new Object(); 2768 message = MessageFactory.createAnswerMessage(messageBody.from, "", thisParticipant.contextId, constraintsAux, "", messageBody.connected); 2769 message.body.from = messageBody.from.rtcIdentity; 2770 thisParticipant.identity.messagingStub.sendMessage(message); 2771 2772 } 2773 else{ 2774 if(!messageBody) message = MessageFactory.createAnswerMessage(this.me.identity,this.identity,this.contextId, constraintsAux); 2775 else message = MessageFactory.createAnswerMessage(this.me.identity,this.identity,this.contextId, constraintsAux, messageBody.hosting); 2776 2777 setStatus(ParticipantStatus.ACCEPTED); 2778 2779 this.RTCPeerConnection.createAnswer(function (sessionDescription) { 2780 thisParticipant.RTCPeerConnection.setLocalDescription(sessionDescription, function () { 2781 console.log("Local description set: ", sessionDescription); 2782 message.body.connectionDescription = thisParticipant.RTCPeerConnection.localDescription; 2783 2784 if (!thisParticipant.identity.messagingStub){ 2785 errorCallback("Messaging Stub not well initialized"); 2786 return; 2787 } 2788 else thisParticipant.identity.messagingStub.sendMessage(message); 2789 2790 if (callback) 2791 callback(); 2792 2793 }, function(error){errorCallback(error)}); 2794 }, function(error){ 2795 console.log("error: ", error); 2796 }, sdpConstraints); 2797 } 2798 2799 break; 2800 case MessageType.CONNECTIVITY_CANDIDATE: 2801 //console.log("Missing data for connectivity candidate", messageBody); 2802 if(messageBody.lastCandidate){ 2803 if(errorCallback) 2804 errorCallback("Missing data for connectivity candidate"); 2805 // SD: bugfix, we also need context.id etc. in this message 2806 message = MessageFactory.createCandidateMessage( this.me.identity,this.identity,this.contextId,"",messageBody.id,messageBody.connectionDescription,true); 2807 thisParticipant.identity.messagingStub.sendMessage(message); 2808 return; 2809 } 2810 else message = MessageFactory.createCandidateMessage(this.me.identity,this.identity,this.contextId,messageBody.label,messageBody.id,messageBody.candidateDescription,messageBody.lastCandidate); 2811 2812 if (!thisParticipant.identity.messagingStub){ 2813 errorCallback("Messaging Stub not well initialized"); 2814 return; 2815 } 2816 else thisParticipant.identity.messagingStub.sendMessage(message); 2817 break; 2818 case MessageType.BYE: 2819 2820 2821 message = new Message(this.me.identity,this.identity,"",MessageType.BYE,this.contextId); 2822 if (!thisParticipant.identity.messagingStub){ 2823 errorCallback("Messaging Stub not well initialized"); 2824 return; 2825 } 2826 else 2827 { 2828 thisParticipant.identity.messagingStub.sendMessage(message); 2829 setStatus(ParticipantStatus.NOT_PARTICIPATING); // TODO: CHECK IF ITS THE CORRECT STATE 2830 } 2831 console.log("Call terminated"); 2832 break; 2833 case MessageType.REMOVE_PARTICIPANT: 2834 2835 2836 message = new Message(this.me.identity,this.identity,"",MessageType.REMOVE_PARTICIPANT,this.contextId); 2837 if (!thisParticipant.identity.messagingStub){ 2838 errorCallback("Messaging Stub not well initialized"); 2839 return; 2840 } 2841 else 2842 {thisParticipant.identity.messagingStub.sendMessage(message); 2843 setStatus(ParticipantStatus.NOT_PARTICIPATING); // TODO: CHECK IF ITS THE CORRECT STATE 2844 } 2845 console.log("Call terminated"); 2846 break; 2847 case MessageType.UPDATE: 2848 console.log("UPDATE"); 2849 2850 console.log("MESSAGE: ", message); 2851 message = MessageFactory.createUpdateMessage(this.me.identity,this.identity,this.contextId, messageBody.newConstraints); 2852 console.log(message); 2853 2854 this.RTCPeerConnection.createOffer(function (sessionDescription) { 2855 thisParticipant.RTCPeerConnection.setLocalDescription(sessionDescription, function () { 2856 console.log("Local description set: ", sessionDescription); 2857 message.body.newConnectionDescription = thisParticipant.RTCPeerConnection.localDescription; 2858 if (!thisParticipant.identity.messagingStub){ 2859 errorCallback("Messaging Stub not well initialized"); 2860 return; 2861 } 2862 else thisParticipant.identity.messagingStub.sendMessage(message); 2863 2864 }, function(error){errorCallback(error)}); 2865 }, function(error){errorCallback(error)}, sdpConstraints); 2866 2867 break; 2868 2869 case MessageType.UPDATED: 2870 // 2871 console.log("MESSAGE: ", message); 2872 message = MessageFactory.createUpdatedMessage(this.me.identity,this.identity,this.contextId, messageBody.newConstraints); 2873 console.log(message); 2874 2875 console.log("VIDEO REMOTO: ", this.RTCPeerConnection.getRemoteStreams()[0].getVideoTracks()); 2876 console.log("AUDIO REMOTO: ", this.RTCPeerConnection.getRemoteStreams()[0].getAudioTracks()); 2877 2878 2879 this.RTCPeerConnection.createAnswer(function (sessionDescription) { 2880 thisParticipant.RTCPeerConnection.setLocalDescription(sessionDescription, function () { 2881 console.log("Local description set: ", sessionDescription); 2882 message.body.newConnectionDescription = thisParticipant.RTCPeerConnection.localDescription; 2883 2884 if (!thisParticipant.identity.messagingStub){ 2885 errorCallback("Messaging Stub not well initialized"); 2886 return; 2887 } 2888 else thisParticipant.identity.messagingStub.sendMessage(message); 2889 2890 if (callback) 2891 callback(); 2892 2893 }, function(error){errorCallback(error)}); 2894 }, function(error){errorCallback(error)}, sdpConstraints); 2895 break; 2896 } 2897 console.log('Sending: ', message); 2898 } 2899 2900 2901 /** 2902 * 2903 * The Participant leaves the Conversation removing all resources shared in the conversation. 2904 * Participant status is changed accordingly. 2905 * 2906 * @param {boolean} sendMessage - If true a BYE message will be sent to the participant before removing it. If false the participant will be removed locally from the conversation without sending any message. 2907 * 2908 */ 2909 2910 Participant.prototype.leave = function(sendMessage) { 2911 setStatus(ParticipantStatus.PARTICIPATED); 2912 this.identity.messagingStub.removeListener("",this.identity.rtcIdentity,""); 2913 if(this == this.me){ 2914 this.RTCPeerConnection.getLocalStreams().forEach(function(element, index, array){ 2915 array[index].stop(); 2916 }); 2917 } 2918 else{ 2919 if(sendMessage==true) this.sendMessage("",MessageType.BYE,"","",function(){},function(){}); 2920 this.dataBroker.removeDataChannel(this.identity); 2921 this.RTCPeerConnection.close(); 2922 } 2923 } 2924 2925 2926 /** 2927 * Returns the current status of this Participant 2928 * 2929 * @returns ParticipantStatus ... gets the status attribute for a participant 2930 * 2931 */ 2932 Participant.prototype.getStatus = function() { 2933 return this.status; 2934 } 2935 2936 /**@ignore 2937 * setConversation 2938 */ 2939 2940 Participant.prototype.setConversation = function(conversation) { 2941 2942 if (!conversation) 2943 return; 2944 else 2945 this.conversation = conversation; 2946 } 2947 2948 2949 /** 2950 * Adds a Resource to this participant including all the signaling and logical actions required. 2951 * 2952 * @param {ResourceConstraints} resourceConstraints - Array of constraints for the initial resources of the remote participant (CURRENT IMPLEMENTATION WILL TAKE THE FIRST ONE). 2953 * @param {Message} [message] - In case an UPDATE message is received, it should be passed to this function as a parameter to process it and send the UPDATED. 2954 * @param {callback} callback - Callback function fired when the resource was added succesfully. 2955 * @param {errorCallback} errorCallback - Callback function fired when an error happens. 2956 * 2957 */ 2958 Participant.prototype.addResource = function (resourceConstraints, message, callback, errorCallback) { 2959 2960 if (this == this.me) { 2961 // Create the resource and the media if the direction is not "in" 2962 2963 var doGetUserMedia = false; 2964 var doDataChannel = false; 2965 var conversationResource = false; 2966 var constraints = new Object(); 2967 constraints.audio = false; 2968 constraints.video = false; 2969 2970 // make the conditions to update the stream if we had already Webcam or microphone 2971 var micAlready = (this.getResources("",ResourceType.AUDIO_MIC).length!=0); 2972 var camAlready = (this.getResources("",ResourceType.VIDEO_CAM).length!=0); 2973 var micCamAlready = (this.getResources("",ResourceType.AUDIO_VIDEO).length!=0); 2974 // TODO: CHECK FOR DATACHANNEL TYPES AND CONTROL THE CREATION OF A NEW DATACHANNEL, CREATING IT ONLY IF NECCESARY 2975 2976 // TODO: Solve the problem where for many resourceConstraints, we would have a loop with callbacks inside. 2977 resourceConstraints = resourceConstraints[0]; // <dirtyFix> 2978 2979 switch (resourceConstraints.type) { 2980 2981 case ResourceType.AUDIO_MIC: 2982 if(!micAlready & !micCamAlready){ 2983 constraints.audio = true; 2984 doGetUserMedia = true; 2985 } 2986 break; 2987 case ResourceType.VIDEO_CAM: 2988 if(!camAlready & !micCamAlready){ 2989 constraints.video = true; 2990 doGetUserMedia = true; 2991 } 2992 break; 2993 case ResourceType.AUDIO_VIDEO: 2994 if(!camAlready) constraints.video = true; 2995 if(!micAlready) constraints.audio = true; 2996 if(!micCamAlready) doGetUserMedia = true; 2997 break; 2998 case ResourceType.SCREEN: 2999 constraints.audio = false; 3000 constraints.video = { 3001 mandatory: { 3002 chromeMediaSource: 'screen', 3003 maxWidth: 1280, 3004 maxHeight: 720 3005 }, 3006 optional: [] 3007 }; 3008 doGetUserMedia = true; 3009 break; 3010 case ResourceType.FILE: 3011 doDataChannel = true; 3012 break; 3013 case ResourceType.CHAT: 3014 doDataChannel = true; 3015 break; 3016 } 3017 3018 3019 console.log(constraints); 3020 3021 if (doGetUserMedia === true && resourceConstraints.direction != "in") { 3022 var thisParticipant = this; 3023 getUserMedia(constraints, function (stream) { 3024 var evt = new Object(); 3025 evt.stream = stream; 3026 thisParticipant.rtcEvtHandler('onaddlocalstream', evt); 3027 3028 if(!micAlready & !camAlready){ 3029 thisParticipant.RTCPeerConnection.addStream(stream); 3030 var resource = new Resource(resourceConstraints); 3031 resource.id = stream.id; 3032 resource.constraint.constraints= {id: stream.id}; 3033 resource.connections.push(thisParticipant.RTCPeerConnection); 3034 thisParticipant.resources.push(resource); 3035 } 3036 else{ 3037 if(micAlready){ 3038 var resource = thisParticipant.me.getResources("",ResourceType.AUDIO_MIC)[0]; 3039 var stream2 = thisParticipant.RTCPeerConnection.getStreamById(resource.id); 3040 stream2.addTrack(stream.getVideoTracks()[0]); 3041 } 3042 if(camAlready){ 3043 var resource = thisParticipant.me.getResources("",ResourceType.VIDEO_CAM)[0]; 3044 var stream2 = thisParticipant.RTCPeerConnection.getStreamById(resource.id); 3045 stream2.addTrack(stream.getAudioTracks()[0]); 3046 } 3047 resource.type=ResourceType.AUDIO_VIDEO; 3048 resource.constraint.type=ResourceType.AUDIO_VIDEO; 3049 } 3050 3051 callback(); 3052 }, errorCallback); 3053 return; 3054 } 3055 if (doDataChannel === true && resourceConstraints.direction != "in") { 3056 var codec = new Codec(resourceConstraints.type); 3057 if(resourceConstraints.constraints.id) codec.id = resourceConstraints.constraints.id; 3058 var resource = new Resource(resourceConstraints, codec); 3059 resource.connections.push(thisParticipant.RTCPeerConnection); 3060 resource.owner = this.identity; 3061 this.resources.push(resource); 3062 var evt = new Object(); 3063 evt.codec = codec; 3064 this.onRTCEvt('onResourceParticipantAddedEvt', evt); 3065 } 3066 callback(); 3067 } else { 3068 if (resourceConstraints.direction != "out") { 3069 if (doGetUserMedia === true) { 3070 var resource = new Resource(resourceConstraints); 3071 resource.id = resource.constraint.constraints.id; 3072 resource.owner = this.identity; 3073 resource.connections.push(pc); 3074 thisParticipant.resources.push(resource); 3075 } 3076 if (doDataChannel === true) { 3077 var resource = new Resource(resourceConstraints); 3078 //var codec = new Codec(resourceConstraints.type); 3079 //resource.codec = codec; 3080 resource.owner = this.identity; 3081 resource.connections.push(pc); 3082 thisParticipant.resources.push(resource); 3083 } 3084 } 3085 if (resourceConstraints.direction != "in") { 3086 // Get the resource from the me participant and add it to the peerConnection 3087 3088 //TODO: IMPLEMENT AND CHECK!!!!!!!!!!!!!! 3089 var channel; 3090 var thisParticipant = this; 3091 3092 if (resourceConstraints.length > 0) { 3093 // TODO: Solve the problem where for many resourceConstraints, we would have a loop with callbacks inside. 3094 constraints = resourceConstraints[0]; // <dirtyFix> 3095 var media = false; 3096 var data = false; 3097 switch (constraints.type) { 3098 3099 case ResourceType.AUDIO_MIC: 3100 media = true; 3101 break; 3102 case ResourceType.VIDEO_CAM: 3103 media = true; 3104 break; 3105 case ResourceType.AUDIO_VIDEO: 3106 media = true; 3107 break; 3108 case ResourceType.FILE: 3109 data = true; 3110 break; 3111 case ResourceType.CHAT: 3112 data = true; 3113 break; 3114 } 3115 } 3116 3117 // needed to be implemented the resources types 3118 //wiuth the resources types see what do we want to have 3119 //if chat -> codec for chat | if filesharing -> codec for filesharing 3120 3121 // create data channel and setup chat 3122 if (data && constraints.direction != "in") { 3123 console.log("Creating remote peer with local constraints: ", constraints); 3124 channel = thisParticipant.RTCPeerConnection.createDataChannel("dataChannel"); // TODO: CREATE DATACHANNEL ONLY IF THERE IS NOT DATARESOURCE YET. 3125 thisParticipant.setDataBroker(constraints.constraints.dataBroker); 3126 thisParticipant.dataBroker.addDataChannel(channel,thisParticipant.identity); 3127 3128 var resourceData = this.me.getResources(constraints)[0]; 3129 3130 resourceData.connections.push(thisParticipant.RTCPeerConnection); 3131 3132 3133 channel.onopen = function () { 3134 thisParticipant.dataBroker.onDataChannelEvt(); 3135 //thisParticipant.onRTCEvt("onResourceParticipantAddedEvt", resourceData); 3136 } 3137 3138 // setup chat on incoming data channel 3139 pc.ondatachannel = function (evt) { 3140 channel = evt.channel; 3141 }; 3142 } 3143 if (media && constraints.direction != "in") { 3144 var resourceMedia = this.me.getResources(constraints); 3145 3146 //If it doesnt find the constraints means that the audio and video were merged into AudioVideo 3147 if(resourceMedia.length==0){ 3148 constraints.type=ResourceType.AUDIO_VIDEO; 3149 resourceMedia = this.me.getResources(constraints)[0]; 3150 } 3151 3152 var stream = this.me.RTCPeerConnection.getStreamById(resourceMedia[0].id); 3153 thisParticipant.RTCPeerConnection.addStream(stream); 3154 resourceMedia[0].connections.push(thisParticipant.RTCPeerConnection); 3155 } 3156 } 3157 3158 if (!message) { 3159 // setlocal description, send update 3160 var messageBody = new Object(); 3161 messageBody.newConstraints=resourceConstraints; 3162 this.sendMessage(messageBody, MessageType.UPDATE, constraints, callback, errorCallback); 3163 3164 } else { 3165 // set remotedescription, set localdescription send updated 3166 console.log("ADD_RESOURCE <- Participant"); 3167 console.log("message: ", message); 3168 var description = new RTCSessionDescription(message.body.newConnectionDescription); 3169 this.RTCPeerConnection.setRemoteDescription(description, 3170 onSetSessionDescriptionSuccess, onSetSessionDescriptionError); 3171 console.log("Remote Description set: ", description); 3172 console.log("Participant: ", this); 3173 3174 var messageBody = new Object(); 3175 messageBody.newConstraints=resourceConstraints; 3176 this.sendMessage(messageBody, MessageType.UPDATED, constraints, callback, errorCallback); 3177 } 3178 callback(); 3179 } 3180 } 3181 3182 /** 3183 * Searches and retrieves Resources. 3184 * 3185 * @param {ResourceConstraints} [resourceConstraints] - Searches the Resources by constraints. 3186 * @param {ResourceType} [resourceType] - Searches the Resources by type. 3187 * @param {string} id - Searches the Resources by ID. 3188 * 3189 */ 3190 Participant.prototype.getResources = function (resourceConstraints, resourceType, id) { 3191 3192 var resources = new Array(); 3193 3194 if (resourceConstraints) { 3195 this.resources.forEach(function (element, index, array) { 3196 if (element.constraint.type == resourceConstraints.type && element.constraint.direction == resourceConstraints.direction) resources.push(array[index]); 3197 }); 3198 return resources; 3199 } 3200 3201 if (resourceType) { 3202 this.resources.forEach(function (element, index, array) { 3203 if (element.type === resourceType) resources.push(array[index]); 3204 }); 3205 return resources; 3206 } 3207 if (id) { 3208 this.resources.forEach(function (element, index, array) { 3209 if (element.id === id) resources.push(array[index]); 3210 }); 3211 return resources; 3212 } 3213 3214 // If no filter, return all the resources. 3215 return this.resources; 3216 } 3217 3218 /**@ignore 3219 * getStreams 3220 * 3221 * @returns Stream [] ... gets the resources array attribute 3222 * 3223 */ 3224 Participant.prototype.getStreams = function() { 3225 if (this.me.identity.rtcIdentity == this.identity.rtcIdentity) 3226 return this.RTCPeerConnection.getLocalStreams(); 3227 else 3228 return this.RTCPeerConnection.getRemoteStreams(); 3229 } 3230 3231 /** 3232 * Sets the DataBroker to a Participant 3233 * @param {DataBroker} databroker - DataBroker to set. 3234 * 3235 */ 3236 Participant.prototype.setDataBroker = function( databroker ) { 3237 this.dataBroker = databroker; 3238 } 3239 // ------------------------------- 3240 // Conversation.js 3241 // ------------------------------- 3242 3243 /** 3244 * @fileOverview WebRTC Framework to facilitate the development of Applications that seamless interoperate between each other 3245 * @author Paulo Chainho <paulo-g-chainho@ptinovacao.pt> 3246 * @author Steffen Druesedow <Steffen.Druesedow@telekom.de> 3247 * @author Miguel Seijo Simo <Miguel.Seijo@telekom.de> 3248 * @author Vasco Amaral <vasco-m-amaral@ptinovacao.pt> 3249 * @author Kay Haensge <Kay.Haensge@telekom.de> 3250 * @author Luis Oliveira <luis-f-oliveira@ptinovacao.pt> 3251 */ 3252 3253 3254 /** 3255 * Conversation class 3256 * @class 3257 * @param participants list of {@link Participant} involved in the conversation 3258 * @param id Unique Conversation identification 3259 * @param owner the {@link Participant} organizing the conversation 3260 * @param hosting the {@link Identity} that is providing the signalling message server 3261 * @param rtcEvtHandler Event handler implemented by the Application to receive and process RTC events triggered by WebRTC Media Engine 3262 * @param msgHandler {@link Message} handler implemented by the Application to receive and process Messages from the {@link MessagingStub} 3263 * 3264 */ 3265 function Conversation(myIdentity, rtcEvtHandler, msgHandler, iceServers, callback, errorCallback) { 3266 /** 3267 * The list of {@link Participant} in the Conversation. 3268 * @private 3269 * @type Participant[] 3270 */ 3271 this.participants = []; 3272 3273 /** 3274 * Unique Conversation identification. 3275 * @private 3276 * @type string 3277 */ 3278 this.id; 3279 3280 /** 3281 * The {@link Participant} that manages the Conversation having additional features 3282 * Eg, add and remove participants, mute/unmute/share resources, etc. 3283 * It may change in the middle of the conversation by sending a "OfferRole" {@link Message}. 3284 * @private 3285 * @type Participant 3286 */ 3287 this.owner; 3288 this.rtcEvtHandler = rtcEvtHandler; 3289 this.msgHandler = msgHandler; 3290 3291 this.dataBroker = new DataBroker(); 3292 3293 /** 3294 * The {@link Identity} that is providing the conversation signalling messaging server. 3295 * @private 3296 * @type Identity 3297 */ 3298 this.hosting; 3299 3300 /** 3301 * ICE servers setup data. 3302 * @private 3303 * @type String 3304 */ 3305 this.iceServers=iceServers; 3306 3307 /* 3308 * TODO: 3309 * - hosting could be empty, in this case the communication is purely P2P 3310 * - if hosting is NOT empty, then the Conversation has to add a listener to the MessagingStub 3311 * of the hosting-identity 3312 * - if hosting is empty, then the participants are invoking addListener of 3313 */ 3314 this.resources = []; 3315 //this.status; 3316 //this.recording; 3317 //this.subject; 3318 //this.agenda = []; 3319 //this.startingTime; 3320 //this.duration; 3321 3322 //this.eventHandler; 3323 this.myParticipant = new Participant(); 3324 this.myParticipant.identity = myIdentity; 3325 thisConversation = this; 3326 } 3327 3328 3329 3330 /** 3331 * A Conversation is opened for invited participants. 3332 * Creates the remote participant, resolves and gets the stub, 3333 * creates the peer connection, connects to the stub and sends invitation 3334 * 3335 * @param {string[]} rtcIdentity list of users to be invited 3336 * @param {string} [invitation] body to be attached to INVITATION {@link MESSAGE} 3337 * @@callback callback to handle responses triggered by this operation 3338 */ 3339 Conversation.prototype.open = function (rtcIdentity, resourceConstraints, invitationBody, callback, errorCallback) { 3340 /* TODO: 3341 In the following code from the clients doCall() method it seems that the peers and invitation params hold 3342 redundant information - just the peers. 3343 ... 3344 var peers = document.getElementById('callTo').value.split(";"); 3345 conversation = new Conversation(myIdentity, this.onRTCEvt.bind(this), 3346 this.onMessage.bind(this), iceServers); 3347 var invitation = new Object(); 3348 invitation.peers = peers; 3349 conversation.open(peers, constraints, invitation); 3350 ... 3351 */ 3352 var that = this; 3353 3354 this.myParticipant.createMyself(this.myParticipant.identity, resourceConstraints, this.onRTCEvt.bind(this), this.onMessage.bind(this), function () { 3355 3356 that.id = "context-" + guid(); 3357 that.owner = that.myParticipant; 3358 that.owner.contextId = that.id; 3359 3360 that.myParticipant.contextId = that.id; 3361 if (that.myParticipant.hosting === "") 3362 that.myParticipant.hosting = that.myParticipant.identity; 3363 3364 var localParticipant = that.myParticipant; 3365 var localIDP = localParticipant.identity.idp; 3366 var toIdentity; 3367 //add a verification if rtcIdentity is already an identity or if is a rtcIdentity only 3368 // 3369 localIDP.createIdentities(rtcIdentity, function (identity) { 3370 console.log("rtcIdentity: ", identity); 3371 if (identity instanceof Array) { 3372 identity.forEach(function (element, index, array) { 3373 var participant = new Participant(); 3374 console.log("owner: ", this.owner); 3375 toIdentity = element; 3376 3377 participant.hosting = that.myParticipant.hosting; 3378 3379 console.log("Calling to Identity: ", toIdentity); 3380 3381 console.log("Created remote participant: ", participant); 3382 /*var constraints = new Array(); 3383 that.resources.every(function (element, index, array) { 3384 constraints.push(element.constraint); 3385 }); 3386 that.myParticipant.resources.every(function (element, index, array) { 3387 constraints.push(element.constraint); 3388 });*/ 3389 3390 participant.setDataBroker(that.dataBroker); 3391 participant.createRemotePeer(toIdentity, localParticipant, that.id, resourceConstraints, that.onRTCEvt.bind(that), that.onMessage.bind(that), that.iceServers); 3392 that.addParticipant(participant, invitationBody, resourceConstraints); 3393 }); 3394 } 3395 }); 3396 3397 3398 3399 3400 3401 }, errorCallback); 3402 }; 3403 3404 /** 3405 * Opens a conversation by accepting an incoming invitation. 3406 * Sends the message to the addressed participant (the one who sent the invitation) 3407 * Sets the Conversation status to OPENED. 3408 * @param {Message} invitation the invitation message received for the accepted conversation 3409 * @param {string} answer the answer to be sent with the accepted message 3410 * @param {string} [constraints] any constraint on how the invitation was accepted e.g. only audio but not video 3411 * @callback callback the callback that handles events triggered by this function 3412 */ 3413 Conversation.prototype.acceptInvitation = function(recvInvitation, answerBody, callback, errorCallback) { 3414 3415 // Swap direction because we are receiving 3416 var direction = "in_out"; 3417 /* if(recvInvitation.body.constraints[0].direction=="in") direction="out"; 3418 if(recvInvitation.body.constraints[0].direction=="out") direction="in"; 3419 recvInvitation.body.constraints[0].direction=direction; 3420 onsole.log("CONVERSATION..> acceptInvitation: ", recvInvitation);*/ 3421 if (!this.setStatus(ConversationStatus.OPENED)) { 3422 // TODO: ERROR, Status cant be changed 3423 return; 3424 } 3425 3426 var that = this; 3427 var chatID = new Object(); 3428 var videoID = new Object(); 3429 var fileID = new Object(); 3430 var iteration; 3431 var i; 3432 for(i=0;i<recvInvitation.body.constraints.length;i++){ 3433 if(recvInvitation.body.constraints[i].type == "file"){ 3434 fileID.id = recvInvitation.body.constraints[i].id; 3435 fileID.index = i; 3436 }else{ 3437 if(recvInvitation.body.constraints[i].type == "chat"){ 3438 chatID.id = recvInvitation.body.constraints[i].id; 3439 chatID.index = i; 3440 }else{ 3441 if(recvInvitation.body.constraints[i].type == "audioVideo"){ 3442 videoID.id = recvInvitation.body.constraints[i].id; 3443 videoID.index = i; 3444 } 3445 } 3446 } 3447 } 3448 this.myParticipant.createMyself(this.myParticipant.identity, recvInvitation.body.constraints, this.onRTCEvt.bind(this), this.onMessage.bind(this), function () { 3449 //TODO: THINK OF MULTIPARTY CASE, YOU RECEIVE A CALL BUT THE INVITE IS FOR MANY PEOPLE 3450 that.id = recvInvitation.contextId; 3451 //this.owner.contextId=this.id; 3452 that.myParticipant.contextId=that.id; 3453 3454 //var participant = new Participant(); 3455 //var localParticipant = this.owner; 3456 console.log("CONVERSATION > SS1: ", that.myParticipant.hosting); 3457 if(that.myParticipant.hosting == null){ 3458 that.myParticipant.hosting = recvInvitation.from; 3459 console.log("CONVERSATION > SS: ", that.myParticipant.hosting); 3460 console.log("CONVERSATION > SS: ", recvInvitation.from); 3461 } 3462 3463 3464 var localParticipant = that.myParticipant; 3465 //console.log("CONSTRAINTS AFTER!!!!:" + recvInvitation.body.constraints[0].constraints.id); 3466 3467 3468 var localIDP = localParticipant.identity.idp; 3469 var toIdentity; 3470 3471 var constraints = recvInvitation.body.constraints; // <dirtyFix> 3472 3473 console.log("recvInvitation.body.constraints: ", recvInvitation.body.constraints); 3474 for(iteration=0;iteration<constraints.length;iteration++){ 3475 if(constraints[iteration].type==ResourceType.CHAT || constraints[iteration].type==ResourceType.FILE){ 3476 //beginof: create a codec with the data received 3477 var codec=new Codec(constraints[iteration].constraints.type,constraints[iteration].constraints.CodecLibUrl); 3478 console.log("constraints.type==ResourceType.CHAT: ", that.myParticipant.resources); 3479 that.myParticipant.resources[iteration].codec.id=constraints[iteration].constraints.id; 3480 var resource = new Resource(constraints[iteration], codec); 3481 resource.codec.setDataBroker(that.dataBroker); 3482 //endof: create a codec with the data received 3483 } 3484 } 3485 //constraints = new Array(constraints); // </dirtyFix> 3486 3487 //Create an array to all peers that I want to connect 3488 //recvInvitation.body.peers[i] is defined when the clients are selected in the application 3489 var peers = new Array(); 3490 peers.push(recvInvitation.from.rtcIdentity); 3491 console.log("recv: ", recvInvitation); 3492 for(var i = 0; i < recvInvitation.body.peers.length; i++){ 3493 if(recvInvitation.body.peers[i] !== that.myParticipant.identity.rtcIdentity) 3494 peers.push(recvInvitation.body.peers[i]); 3495 } 3496 3497 //now should be createIdentities because of multiparty 3498 localIDP.createIdentities(peers, function(identity){ 3499 3500 if(identity instanceof Array){ 3501 console.log("Identity: ", identity); 3502 3503 identity.forEach(function(element, index, array){ 3504 var participant = new Participant(); 3505 3506 toIdentity = element; 3507 that.hosting = recvInvitation.body.hosting; 3508 console.log("THIS.HOSTING", recvInvitation.body.hosting); 3509 if(typeof that.owner === 'undefined'){ 3510 that.owner = participant; 3511 } 3512 participant.hosting = that.owner; 3513 if(that.hosting == recvInvitation.from.rtcIdentity){ 3514 console.log("THIS.HOSTING", that.hosting); 3515 console.log("THIS.OWNER", that.owner); 3516 toIdentity.messagingStub = recvInvitation.from.messagingStub; 3517 } 3518 else{ 3519 toIdentity.messagingStub = that.myParticipant.identity.messagingStub; 3520 } 3521 3522 3523 participant.setDataBroker(that.dataBroker); 3524 if(chatID.id != null){ 3525 constraints[chatID.index].id = {id: chatID.id}; 3526 } 3527 if(videoID.id != null){ 3528 constraints[videoID.index].id = {id: videoID.id}; 3529 } 3530 if(fileID.id != null){ 3531 constraints[fileID.index].id = {id: fileID.id}; 3532 } 3533 3534 participant.createRemotePeer(toIdentity, localParticipant, that.id, constraints,that.onRTCEvt.bind(that), that.onMessage.bind(that), that.iceServers); 3535 that.participants.push(participant); 3536 if(recvInvitation.from.rtcIdentity === toIdentity.rtcIdentity){ 3537 //Only do the RTCPeerConnection to the identity that is inviting 3538 //for the other identities only creates the participants 3539 console.log("ENTREI > acceptInvitation: ", recvInvitation); 3540 var description = new RTCSessionDescription(recvInvitation.body.connectionDescription); 3541 participant.RTCPeerConnection.setRemoteDescription(description, onSetSessionDescriptionSuccess, onSetSessionDescriptionError); 3542 } 3543 participant.connectStub(function() { 3544 if(recvInvitation.from.rtcIdentity === toIdentity.rtcIdentity){ 3545 participant.sendMessage(answerBody, MessageType.ACCEPTED, constraints, callback, errorCallback); 3546 } 3547 3548 }); 3549 }, 3550 function(error){ 3551 console.log(error); 3552 }); 3553 } 3554 }); 3555 3556 }, errorCallback); 3557 }; 3558 3559 3560 /** 3561 * If to-field of the message is empty, then send message to all participants, send only to specified participants 3562 * if to-field is filled. 3563 * (Message.to-field is a list of identities.) 3564 * @param {MESSAGE} message the {@link Message} to be sent to the specified Identities or or ALL participants 3565 */ 3566 Conversation.prototype.sendMessage = function(message) { 3567 if (!message) 3568 return; 3569 3570 /* 3571 * TODO: 3572 * if this.hosting is set, then ALL Messages are send via the MessagingStub 3573 * of the hosting identity. 3574 * Only if this.hosting is NOT SET we iterate through the participants 3575 */ 3576 3577 if (this.hosting) { 3578 // seems that there is a special Identity assigned to "host" this conversation 3579 // in this case ALL Messages must be sent via the messagingStub of this identity 3580 // TODO: Check following ASSUMPTION: 3581 // Only one message is sent to the Messaging stub of the hosting-identity. The MessagingServer is responsible 3582 // to forward individual messages to all attached participants of this conversation. 3583 var p = this.getParticipant(this.hosting); 3584 if (p) 3585 p.sendMessage(message); 3586 } 3587 else { 3588 // send to all participants via their own messaging stubs, if to is not set or empty 3589 if (!message.to) { 3590 for (var p in this.participants) 3591 this.participants[p].sendMessage(message); 3592 } 3593 else { 3594 // send to all participants that we have for the given identities 3595 for (var i in message.to) { 3596 // check for participant matching this identity 3597 var p = this.getParticipant(message.to[i]); 3598 if (p) 3599 p.sendMessage(message); 3600 } 3601 } 3602 } 3603 }; 3604 3605 /** @ignore 3606 * Set the status of this conversation. 3607 * TODO: This method should be private and only be changed by internal state-changes 3608 * @param status ConversationStatus ... the new status of the conversation 3609 */ 3610 Conversation.prototype.setStatus = function(status) { 3611 // DONE: implement the state machine checks here !!! 3612 // TODO: PAUSED AND STOPPED are not in the Enums !!! 3613 // TODO: Change the UML so returns true or false if the state change is not allowed. 3614 // (@Vasco) the previous state machine verifications it was not working in a correct way 3615 // Changed verify below 3616 console.log("In setStatus"); 3617 switch (this.status) { 3618 case ConversationStatus.CREATED: 3619 if (status != ConversationStatus.OPENED) { 3620 console.log("transition is not permited " + status + " actual state: " + this.status); 3621 return false; 3622 } else { 3623 this.status = status; 3624 return true; 3625 } 3626 case ConversationStatus.OPENED: 3627 if (status != ConversationStatus.ACTIVE && status != ConversationStatus.INACTIVE && status != ConversationStatus.FAILED && status != ConversationStatus.PLAYING && status != ConversationStatus.CLOSED) { 3628 console.log("transition is not permited " + status + " actual state: " + this.status); 3629 return false; 3630 } else { 3631 this.status = status; 3632 return true; 3633 } 3634 case ConversationStatus.INACTIVE: 3635 if (status != ConversationStatus.ACTIVE) { 3636 console.log("transition is not permited " + status + " actual state: " + this.status); 3637 return false; 3638 } else { 3639 this.status = status; 3640 return true; 3641 } 3642 case ConversationStatus.FAILED: 3643 console.log("transition is not permited"); 3644 return false; 3645 case ConversationStatus.ACTIVE: 3646 if (status != ConversationStatus.INACTIVE && status != ConversationStatus.CLOSED && status != ConversationStatus.FAILED && status != ConversationStatus.RECORDING && status != ConversationStatus.PLAYING) { 3647 return false; 3648 console.log("transition is not permited " + status + " actual state: " + this.status); 3649 } else { 3650 this.status = status; 3651 return true; 3652 } 3653 case ConversationStatus.CLOSED: 3654 console.log("transition is not permited " + status + " actual state: " + this.status); 3655 return false; 3656 case ConversationStatus.RECORDING: 3657 if (status != ConversationStatus.FAILED && status != ConversationStatus.INACTIVE && status != ConversationStatus.CLOSED && status != ConversationStatus.PLAYING) { 3658 console.log("transition is not permited " + status + " actual state: " + this.status); 3659 return false; 3660 } else { 3661 this.status = status; 3662 return true; 3663 } 3664 case ConversationStatus.PLAYING: 3665 if (status != ConversationStatus.PAUSED && status != ConversationStatus.STOPPED && status != ConversationStatus.INACTIVE && status != ConversationStatus.ACTIVE) { 3666 console.log("transition is not permited " + status + " actual state: " + this.status); 3667 return false; 3668 } else { 3669 this.status = status; 3670 return true; 3671 } 3672 case ConversationStatus.PAUSED: 3673 if (status != ConversationStatus.PLAYING && status != ConversationStatus.STOPPED && status != ConversationStatus.INACTIVE && status != ConversationStatus.ACTIVE) { 3674 console.log("transition is not permited " + status + " actual state: " + this.status); 3675 return false; 3676 } else { 3677 this.status = status; 3678 return true; 3679 } 3680 case ConversationStatus.STOPPED: 3681 console.log("transition is not permited " + status + " actual state: " + this.status); 3682 return false; 3683 default: 3684 if (!this.status) { 3685 this.status = status; 3686 return true; 3687 } 3688 return false; 3689 } 3690 }; 3691 3692 /** 3693 * Returns the status of this conversation 3694 */ 3695 Conversation.prototype.getStatus = function() { 3696 return(this.status); 3697 }; 3698 3699 3700 /** 3701 * Close the conversation with the given message. 3702 * Sends this message to ALL participants and sets the conversation status to CLOSED 3703 * @param {Message} message the final message to be sent to ALL participants of this conversation 3704 * @return {boolean} True if successful, false if the participant is not the owner. 3705 */ 3706 Conversation.prototype.close = function() { 3707 if(this.owner==this.myParticipant) 3708 { 3709 this.participants.forEach(function(element,index,array){ 3710 element.status=ParticipantStatus.PARTICIPATED; 3711 element.identity.messagingStub.removeListener("",element.identity.rtcIdentity,""); 3712 element.sendMessage("",MessageType.REMOVE_PARTICIPANT,"","",function(){},function(){}); 3713 element.RTCPeerConnection.close(); 3714 }); 3715 this.myParticipant.leave(false); 3716 this.setStatus(ConversationStatus.CLOSED); 3717 return true; 3718 } 3719 return false; 3720 } 3721 3722 3723 /** 3724 * The current user invokes bye, if he wants to leave an ongoing conversation. 3725 * Other participants might stay in this conversation, in case that it was a multi-party call with 3726 * more participants. </br> 3727 * Sends a REMOVE_PARTICIPANT message to ALL participants and sets the conversation status to CLOSED. 3728 */ 3729 Conversation.prototype.bye = function() { 3730 this.participants.forEach(function(element,index,array){ 3731 element.leave(true); 3732 delete array[index]; 3733 }); 3734 this.myParticipant.leave(false); 3735 this.setStatus(ConversationStatus.CLOSED); 3736 }; 3737 3738 3739 /** 3740 * Adds a participant to the conversation. 3741 * @param {Participant} participant the {@link Participant} to add to the conversation 3742 * @param {String} [invitation] the invitation to be attached to the {@link Message} body 3743 */ 3744 Conversation.prototype.addParticipant = function(participant, invitationBody, constraints, callback, callbackError) { 3745 this.participants.push(participant); 3746 participant.connectStub(function() { // @pchainho: why do we need this? 3747 participant.sendMessage(invitationBody, MessageType.INVITATION, constraints, callback, callbackError) 3748 }); 3749 }; 3750 3751 /*** Returns the participants of the conversation as an Array. 3752 * @returns {Participants[]} 3753 */ 3754 Conversation.prototype.getParticipants = function() { 3755 return(this.participants); 3756 }; 3757 3758 3759 /**@ignore 3760 * Callback for events from the Participants (received via the MessagingStub) 3761 * @param message : Message 3762 */ 3763 Conversation.prototype.onMessage = function(message) { 3764 // TODO: implement eventHandling 3765 switch (message.type) { 3766 3767 case MessageType.ACCEPTED: 3768 console.log("MESSAGEACCEPTEDCONVERSATION: ", message); 3769 // TODO: change state of the conversation and forward to app-layer 3770 break; 3771 case MessageType.CONNECTIVITY_CANDIDATE: 3772 3773 // put candidate to PC 3774 break; 3775 case MessageType.NOT_ACCEPTED: 3776 this.participants.forEach(function(element, index, array){ 3777 if(element.status==ParticipantStatus.PARTICIPATED){ 3778 array.splice(index, 1); 3779 } 3780 }); 3781 if(this.participants.length==0) this.bye(); 3782 break; 3783 case MessageType.CANCEL: 3784 break; 3785 case MessageType.ADD_RESOURCE: 3786 break; 3787 case MessageType.UPDATE: 3788 break; 3789 case MessageType.UPDATED: 3790 break; 3791 case MessageType.REDIRECT: 3792 break; 3793 case MessageType.BYE: 3794 this.participants.forEach(function(element, index, array){ 3795 if(element.status==ParticipantStatus.PARTICIPATED){ 3796 array.splice(index, 1); 3797 } 3798 }); 3799 if(this.participants.length==0) this.bye(); 3800 break; 3801 case MessageType.OFFER_ROLE: // set new moderator of the conversatoin 3802 break; 3803 case MessageType.INVITATION: 3804 // IF RECEIVED, SOMETHING IS WRONG 3805 /*INVITATION 3806 Participant should only receive invitations in multiparty conversation. In this case it will be automatically accepted, the peerconnection is set and the Accepted message sent.*/ 3807 3808 break; 3809 case MessageType.RESOURCE_REMOVED: 3810 break; 3811 case MessageType.REMOVE_PARTICIPANT: 3812 // Remove everyone without sending BYE ("silently") 3813 this.participants.forEach(function (element, index, array) { 3814 element.leave(false); 3815 delete array[index]; 3816 }); 3817 this.myParticipant.leave(false); 3818 this.setStatus(ConversationStatus.CLOSED); 3819 break; 3820 case MessageType.SHARE_RESOURCE: 3821 break; 3822 default: 3823 // forward to application level 3824 break; 3825 } 3826 this.msgHandler(message); 3827 }; 3828 3829 /** @ignore */ 3830 Conversation.prototype.onRTCEvt = function(event, evt) { 3831 // TODO To implement and pass the events up 3832 switch (event) { 3833 3834 case 'onnegotiationneeded': 3835 this.rtcEvtHandler(event, evt); 3836 break; 3837 case 'onicecandidate': 3838 this.rtcEvtHandler(event, evt); 3839 break; 3840 case 'onsignalingstatechange': 3841 this.rtcEvtHandler(event, evt); 3842 break; 3843 case 'onaddstream': 3844 this.rtcEvtHandler(event, evt); 3845 break; 3846 case 'onaddlocalstream': 3847 this.rtcEvtHandler(event,evt); 3848 break; 3849 case 'onremovestream': 3850 this.rtcEvtHandler(event, evt); 3851 break; 3852 case 'oniceconnectionstatechange': 3853 this.rtcEvtHandler(event, evt); 3854 break; 3855 case 'ondatachannel': 3856 this.rtcEvtHandler(event, evt); 3857 break; 3858 default: 3859 this.rtcEvtHandler(event, evt); 3860 break; 3861 3862 } 3863 }; 3864 3865 /**@ignore 3866 * Records a conversation. 3867 */ 3868 Conversation.prototype.record = function() { 3869 // TODO: to be re-fined and implemented 3870 }; 3871 3872 /**@ignore 3873 * Playback a (part of a) conversation. 3874 * @param timing : ??? ... time index information 3875 * @param resources : Resource[1..*] ... the resource to be played back 3876 */ 3877 Conversation.prototype.play = function(timing, resources) { 3878 // TODO: to be re-fined and implemented 3879 }; 3880 3881 /**@ignore 3882 * Pause playback of a conversation. 3883 */ 3884 Conversation.prototype.pause = function() { 3885 // TODO: to be re-fined and implemented 3886 }; 3887 3888 3889 /**@ignore 3890 * Helper function to get the matching Particpant for a given Identity. 3891 * @param identity : Identity ... the identity to search for 3892 * @returns the Particpant of the current conversation that matches the given Identity 3893 */ 3894 Conversation.prototype.getParticipant = function(identity) { 3895 if (!identity) 3896 return; 3897 var match; 3898 for (var p in this.participants) 3899 if (this.participants[p].identity.rtcIdentity == identity.rtcIdentity) 3900 match = this.participants[p]; 3901 return match; 3902 }; 3903 3904 /** 3905 * ConversationAddResource 3906 */ 3907 Conversation.prototype.addResource = function(resourceConstraints, message, onSuccessCallback, onErrorCallback) { 3908 3909 //see what's in the resource (resourceConstraints) 3910 var thisConversation = this; 3911 // If it comes with a message, means we add a resource from an incoming petition to the corresponding participant. 3912 if(!message){ 3913 var count=0; 3914 var internalSuccessCallback = function(){ 3915 if(count<thisConversation.participants.length){ 3916 count++; 3917 console.log("Adding the resource for the participant number: " + count); 3918 thisConversation.participants[count-1].addResource(resourceConstraints,"",internalSuccessCallback); 3919 } 3920 else{ 3921 onSuccessCallback(); 3922 } 3923 3924 } 3925 3926 thisConversation.myParticipant.addResource(resourceConstraints,"",internalSuccessCallback); 3927 } 3928 else{ 3929 // Swap direction because we are receiving 3930 var direction = "in_out"; 3931 if(message.body.newConstraints[0].direction=="in") direction="out"; 3932 if(message.body.newConstraints[0].direction=="out") direction="in"; 3933 message.body.newConstraints[0].direction=direction; 3934 3935 thisConversation.myParticipant.addResource(resourceConstraints,"",function() { 3936 thisConversation.getParticipant(message.from).addResource(resourceConstraints,message,onSuccessCallback,onErrorCallback); 3937 }); 3938 3939 } 3940 }; 3941 3942 Conversation.reject = function(message){ 3943 message.from.resolve(function(stub){stub.sendMessage(MessageFactory.createNotAccepted(message))}); 3944 3945 }