2014-08-27 21:57:54 +04:00
/ *
2014-09-03 20:29:13 +04:00
Copyright 2014 OpenMarket Ltd
2014-08-27 21:57:54 +04:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
'use strict' ;
2014-08-29 14:29:36 +04:00
var forAllVideoTracksOnStream = function ( s , f ) {
var tracks = s . getVideoTracks ( ) ;
for ( var i = 0 ; i < tracks . length ; i ++ ) {
f ( tracks [ i ] ) ;
}
}
var forAllAudioTracksOnStream = function ( s , f ) {
var tracks = s . getAudioTracks ( ) ;
for ( var i = 0 ; i < tracks . length ; i ++ ) {
f ( tracks [ i ] ) ;
}
}
var forAllTracksOnStream = function ( s , f ) {
forAllVideoTracksOnStream ( s , f ) ;
forAllAudioTracksOnStream ( s , f ) ;
}
2014-09-09 17:53:47 +04:00
navigator . getUserMedia = navigator . getUserMedia || navigator . webkitGetUserMedia || navigator . mozGetUserMedia ;
window . RTCPeerConnection = window . RTCPeerConnection || window . webkitRTCPeerConnection ; // but not mozRTCPeerConnection because its interface is not compatible
window . RTCSessionDescription = window . RTCSessionDescription || window . webkitRTCSessionDescription || window . mozRTCSessionDescription ;
window . RTCIceCandidate = window . RTCIceCandidate || window . webkitRTCIceCandidate || window . mozRTCIceCandidate ;
var createPeerConnection = function ( ) {
var stunServer = 'stun:stun.l.google.com:19302' ;
if ( window . mozRTCPeerConnection ) {
return new window . mozRTCPeerConnection ( { 'url' : stunServer } ) ;
} else {
return new window . RTCPeerConnection ( { "iceServers" : [ { "urls" : "stun:stun.l.google.com:19302" } ] } ) ;
}
}
2014-08-27 21:57:54 +04:00
angular . module ( 'MatrixCall' , [ ] )
2014-08-29 21:01:01 +04:00
. factory ( 'MatrixCall' , [ 'matrixService' , 'matrixPhoneService' , '$rootScope' , function MatrixCallFactory ( matrixService , matrixPhoneService , $rootScope ) {
2014-08-27 21:57:54 +04:00
var MatrixCall = function ( room _id ) {
this . room _id = room _id ;
this . call _id = "c" + new Date ( ) . getTime ( ) ;
2014-08-28 22:03:34 +04:00
this . state = 'fledgling' ;
2014-09-06 03:14:02 +04:00
this . didConnect = false ;
2014-08-27 21:57:54 +04:00
}
2014-09-09 20:37:50 +04:00
MatrixCall . prototype . placeCall = function ( config ) {
2014-08-27 21:57:54 +04:00
self = this ;
matrixPhoneService . callPlaced ( this ) ;
2014-09-09 20:37:50 +04:00
navigator . getUserMedia ( { audio : config . audio , video : config . video } , function ( s ) { self . gotUserMediaForInvite ( s ) ; } , function ( e ) { self . getUserMediaFailed ( e ) ; } ) ;
this . state = 'wait_local_media' ;
2014-09-06 03:14:02 +04:00
this . direction = 'outbound' ;
2014-09-09 20:37:50 +04:00
this . config = config ;
2014-08-27 21:57:54 +04:00
} ;
2014-08-28 22:03:34 +04:00
MatrixCall . prototype . initWithInvite = function ( msg ) {
this . msg = msg ;
2014-09-09 17:53:47 +04:00
this . peerConn = createPeerConnection ( ) ;
2014-08-28 22:03:34 +04:00
self = this ;
this . peerConn . oniceconnectionstatechange = function ( ) { self . onIceConnectionStateChanged ( ) ; } ;
this . peerConn . onicecandidate = function ( c ) { self . gotLocalIceCandidate ( c ) ; } ;
this . peerConn . onsignalingstatechange = function ( ) { self . onSignallingStateChanged ( ) ; } ;
this . peerConn . onaddstream = function ( s ) { self . onAddStream ( s ) ; } ;
this . peerConn . setRemoteDescription ( new RTCSessionDescription ( this . msg . offer ) , self . onSetRemoteDescriptionSuccess , self . onSetRemoteDescriptionError ) ;
this . state = 'ringing' ;
2014-09-06 03:14:02 +04:00
this . direction = 'inbound' ;
2014-08-28 22:03:34 +04:00
} ;
MatrixCall . prototype . answer = function ( ) {
console . trace ( "Answering call " + this . call _id ) ;
2014-08-27 21:57:54 +04:00
self = this ;
2014-08-28 22:03:34 +04:00
navigator . getUserMedia ( { audio : true , video : false } , function ( s ) { self . gotUserMediaForAnswer ( s ) ; } , function ( e ) { self . getUserMediaFailed ( e ) ; } ) ;
this . state = 'wait_local_media' ;
} ;
2014-08-29 18:18:37 +04:00
MatrixCall . prototype . stopAllMedia = function ( ) {
2014-08-29 16:28:04 +04:00
if ( this . localAVStream ) {
forAllTracksOnStream ( this . localAVStream , function ( t ) {
2014-09-09 17:53:47 +04:00
if ( t . stop ) t . stop ( ) ;
2014-08-29 16:28:04 +04:00
} ) ;
}
if ( this . remoteAVStream ) {
forAllTracksOnStream ( this . remoteAVStream , function ( t ) {
2014-09-09 17:53:47 +04:00
if ( t . stop ) t . stop ( ) ;
2014-08-29 16:28:04 +04:00
} ) ;
}
2014-08-29 18:18:37 +04:00
} ;
MatrixCall . prototype . hangup = function ( ) {
console . trace ( "Ending call " + this . call _id ) ;
this . stopAllMedia ( ) ;
2014-09-09 20:52:01 +04:00
if ( this . peerConn ) this . peerConn . close ( ) ;
2014-08-29 14:29:36 +04:00
2014-09-09 20:37:50 +04:00
this . hangupParty = 'local' ;
2014-08-28 22:03:34 +04:00
var content = {
version : 0 ,
call _id : this . call _id ,
} ;
2014-08-29 16:23:01 +04:00
matrixService . sendEvent ( this . room _id , 'm.call.hangup' , undefined , content ) . then ( this . messageSent , this . messageSendFailed ) ;
2014-08-28 22:03:34 +04:00
this . state = 'ended' ;
} ;
MatrixCall . prototype . gotUserMediaForInvite = function ( stream ) {
2014-09-09 20:58:26 +04:00
if ( ! $rootScope . currentCall || $rootScope . currentCall . state == 'ended' ) return ;
2014-08-29 14:29:36 +04:00
this . localAVStream = stream ;
2014-08-28 22:03:34 +04:00
var audioTracks = stream . getAudioTracks ( ) ;
for ( var i = 0 ; i < audioTracks . length ; i ++ ) {
audioTracks [ i ] . enabled = true ;
}
2014-09-09 17:53:47 +04:00
this . peerConn = createPeerConnection ( ) ;
2014-08-28 22:03:34 +04:00
self = this ;
this . peerConn . oniceconnectionstatechange = function ( ) { self . onIceConnectionStateChanged ( ) ; } ;
this . peerConn . onsignalingstatechange = function ( ) { self . onSignallingStateChanged ( ) ; } ;
2014-08-27 21:57:54 +04:00
this . peerConn . onicecandidate = function ( c ) { self . gotLocalIceCandidate ( c ) ; } ;
2014-08-28 22:03:34 +04:00
this . peerConn . onaddstream = function ( s ) { self . onAddStream ( s ) ; } ;
this . peerConn . addStream ( stream ) ;
2014-08-27 21:57:54 +04:00
this . peerConn . createOffer ( function ( d ) {
self . gotLocalOffer ( d ) ;
} , function ( e ) {
self . getLocalOfferFailed ( e ) ;
} ) ;
2014-09-08 19:10:36 +04:00
$rootScope . $apply ( function ( ) {
self . state = 'create_offer' ;
} ) ;
2014-08-28 22:03:34 +04:00
} ;
MatrixCall . prototype . gotUserMediaForAnswer = function ( stream ) {
2014-09-09 20:58:26 +04:00
if ( ! $rootScope . currentCall || $rootScope . currentCall . state == 'ended' ) return ;
2014-08-29 14:29:36 +04:00
this . localAVStream = stream ;
2014-08-28 22:03:34 +04:00
var audioTracks = stream . getAudioTracks ( ) ;
for ( var i = 0 ; i < audioTracks . length ; i ++ ) {
audioTracks [ i ] . enabled = true ;
}
this . peerConn . addStream ( stream ) ;
self = this ;
var constraints = {
'mandatory' : {
'OfferToReceiveAudio' : true ,
'OfferToReceiveVideo' : false
} ,
} ;
this . peerConn . createAnswer ( function ( d ) { self . createdAnswer ( d ) ; } , function ( e ) { } , constraints ) ;
2014-09-08 19:10:36 +04:00
$rootScope . $apply ( function ( ) {
self . state = 'create_answer' ;
} ) ;
2014-08-27 21:57:54 +04:00
} ;
MatrixCall . prototype . gotLocalIceCandidate = function ( event ) {
console . trace ( event ) ;
if ( event . candidate ) {
var content = {
version : 0 ,
call _id : this . call _id ,
candidate : event . candidate
} ;
2014-08-29 16:23:01 +04:00
matrixService . sendEvent ( this . room _id , 'm.call.candidate' , undefined , content ) . then ( this . messageSent , this . messageSendFailed ) ;
2014-08-27 21:57:54 +04:00
}
}
MatrixCall . prototype . gotRemoteIceCandidate = function ( cand ) {
2014-08-28 22:03:34 +04:00
console . trace ( "Got ICE candidate from remote: " + cand ) ;
var candidateObject = new RTCIceCandidate ( {
sdpMLineIndex : cand . label ,
candidate : cand . candidate
} ) ;
this . peerConn . addIceCandidate ( candidateObject , function ( ) { } , function ( e ) { } ) ;
} ;
MatrixCall . prototype . receivedAnswer = function ( msg ) {
this . peerConn . setRemoteDescription ( new RTCSessionDescription ( msg . answer ) , self . onSetRemoteDescriptionSuccess , self . onSetRemoteDescriptionError ) ;
this . state = 'connecting' ;
2014-08-27 21:57:54 +04:00
} ;
MatrixCall . prototype . gotLocalOffer = function ( description ) {
2014-08-28 22:03:34 +04:00
console . trace ( "Created offer: " + description ) ;
2014-08-27 21:57:54 +04:00
this . peerConn . setLocalDescription ( description ) ;
var content = {
version : 0 ,
call _id : this . call _id ,
offer : description
} ;
2014-08-29 16:23:01 +04:00
matrixService . sendEvent ( this . room _id , 'm.call.invite' , undefined , content ) . then ( this . messageSent , this . messageSendFailed ) ;
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'invite_sent' ;
} ) ;
2014-08-28 22:03:34 +04:00
} ;
MatrixCall . prototype . createdAnswer = function ( description ) {
console . trace ( "Created answer: " + description ) ;
this . peerConn . setLocalDescription ( description ) ;
var content = {
version : 0 ,
call _id : this . call _id ,
answer : description
} ;
2014-08-29 16:23:01 +04:00
matrixService . sendEvent ( this . room _id , 'm.call.answer' , undefined , content ) . then ( this . messageSent , this . messageSendFailed ) ;
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'connecting' ;
} ) ;
2014-08-27 21:57:54 +04:00
} ;
MatrixCall . prototype . messageSent = function ( ) {
} ;
MatrixCall . prototype . messageSendFailed = function ( error ) {
} ;
MatrixCall . prototype . getLocalOfferFailed = function ( error ) {
this . onError ( "Failed to start audio for call!" ) ;
} ;
MatrixCall . prototype . getUserMediaFailed = function ( ) {
this . onError ( "Couldn't start capturing audio! Is your microphone set up?" ) ;
} ;
2014-08-28 22:03:34 +04:00
MatrixCall . prototype . onIceConnectionStateChanged = function ( ) {
2014-09-06 03:14:02 +04:00
if ( this . state == 'ended' ) return ; // because ICE can still complete as we're ending the call
2014-08-28 22:03:34 +04:00
console . trace ( "Ice connection state changed to: " + this . peerConn . iceConnectionState ) ;
2014-08-29 14:29:36 +04:00
// ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet
if ( this . peerConn . iceConnectionState == 'completed' || this . peerConn . iceConnectionState == 'connected' ) {
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
self . didConnect = true ;
} ) ;
2014-08-28 22:03:34 +04:00
}
} ;
MatrixCall . prototype . onSignallingStateChanged = function ( ) {
console . trace ( "Signalling state changed to: " + this . peerConn . signalingState ) ;
} ;
MatrixCall . prototype . onSetRemoteDescriptionSuccess = function ( ) {
console . trace ( "Set remote description" ) ;
} ;
2014-08-27 21:57:54 +04:00
2014-08-28 22:03:34 +04:00
MatrixCall . prototype . onSetRemoteDescriptionError = function ( e ) {
console . trace ( "Failed to set remote description" + e ) ;
} ;
MatrixCall . prototype . onAddStream = function ( event ) {
console . trace ( "Stream added" + event ) ;
2014-08-29 14:29:36 +04:00
var s = event . stream ;
this . remoteAVStream = s ;
var self = this ;
forAllTracksOnStream ( s , function ( t ) {
// not currently implemented in chrome
t . onstarted = self . onRemoteStreamTrackStarted ;
} ) ;
2014-08-29 18:18:37 +04:00
event . stream . onended = function ( e ) { self . onRemoteStreamEnded ( e ) ; } ;
2014-08-29 14:29:36 +04:00
// not currently implemented in chrome
2014-08-29 18:18:37 +04:00
event . stream . onstarted = function ( e ) { self . onRemoteStreamStarted ( e ) ; } ;
2014-08-28 22:03:34 +04:00
var player = new Audio ( ) ;
2014-08-29 14:29:36 +04:00
player . src = URL . createObjectURL ( s ) ;
2014-08-28 22:03:34 +04:00
player . play ( ) ;
} ;
2014-08-29 14:29:36 +04:00
MatrixCall . prototype . onRemoteStreamStarted = function ( event ) {
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
} ) ;
2014-08-29 14:29:36 +04:00
} ;
2014-08-29 18:18:37 +04:00
MatrixCall . prototype . onRemoteStreamEnded = function ( event ) {
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'ended' ;
2014-09-09 20:37:50 +04:00
this . hangupParty = 'remote' ;
2014-09-08 19:10:36 +04:00
self . stopAllMedia ( ) ;
2014-09-09 17:53:47 +04:00
this . peerConn . close ( ) ;
2014-09-08 19:10:36 +04:00
self . onHangup ( ) ;
} ) ;
2014-08-29 18:18:37 +04:00
} ;
2014-08-29 14:29:36 +04:00
MatrixCall . prototype . onRemoteStreamTrackStarted = function ( event ) {
2014-09-08 19:10:36 +04:00
self = this ;
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
} ) ;
2014-08-29 14:29:36 +04:00
} ;
MatrixCall . prototype . onHangupReceived = function ( ) {
this . state = 'ended' ;
2014-09-09 20:37:50 +04:00
this . hangupParty = 'remote' ;
2014-08-29 18:18:37 +04:00
this . stopAllMedia ( ) ;
2014-09-09 17:53:47 +04:00
this . peerConn . close ( ) ;
2014-08-29 14:29:36 +04:00
this . onHangup ( ) ;
} ;
2014-08-27 21:57:54 +04:00
return MatrixCall ;
} ] ) ;