2014-08-27 13:57:54 -04:00
/ *
2014-09-03 12:29:13 -04:00
Copyright 2014 OpenMarket Ltd
2014-08-27 13: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 06: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-08-27 13:57:54 -04:00
angular . module ( 'MatrixCall' , [ ] )
2014-11-03 05:33:38 -05:00
. factory ( 'MatrixCall' , [ 'matrixService' , 'matrixPhoneService' , 'modelService' , '$rootScope' , '$timeout' , function MatrixCallFactory ( matrixService , matrixPhoneService , modelService , $rootScope , $timeout ) {
2014-11-06 11:48:01 -05:00
$rootScope . isWebRTCSupported = function ( ) {
2014-11-07 12:56:28 -05: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 ;
2014-11-06 11:48:01 -05:00
return ! ! ( navigator . getUserMedia || window . RTCPeerConnection || window . RTCSessionDescription || window . RTCIceCandidate ) ;
} ;
2014-09-19 13:22:14 -04:00
2014-08-27 13:57:54 -04:00
var MatrixCall = function ( room _id ) {
this . room _id = room _id ;
this . call _id = "c" + new Date ( ) . getTime ( ) ;
2014-08-28 14:03:34 -04:00
this . state = 'fledgling' ;
2014-09-05 19:14:02 -04:00
this . didConnect = false ;
2014-09-12 13:16:24 -04:00
// a queue for candidates waiting to go out. We try to amalgamate candidates into a single candidate message where possible
this . candidateSendQueue = [ ] ;
this . candidateSendTries = 0 ;
2014-09-17 11:26:35 -04:00
var self = this ;
2014-11-07 12:56:28 -05:00
$rootScope . $watch ( this . getRemoteVideoElement ( ) , function ( oldValue , newValue ) {
2014-09-17 11:26:35 -04:00
self . tryPlayRemoteStream ( ) ;
} ) ;
2014-08-27 13:57:54 -04:00
}
2014-09-24 12:57:34 -04:00
MatrixCall . getTurnServer = function ( ) {
matrixService . getTurnServer ( ) . then ( function ( response ) {
2014-09-25 06:13:32 -04:00
if ( response . data . uris ) {
console . log ( "Got TURN URIs: " + response . data . uris ) ;
MatrixCall . turnServer = response . data ;
$rootScope . haveTurn = true ;
// re-fetch when we're about to reach the TTL
$timeout ( MatrixCall . getTurnServer , MatrixCall . turnServer . ttl * 1000 * 0.9 ) ;
} else {
console . log ( "Got no TURN URIs from HS" ) ;
$rootScope . haveTurn = false ;
}
2014-09-24 12:57:34 -04:00
} , function ( error ) {
console . log ( "Failed to get TURN URIs" ) ;
MatrixCall . turnServer = { } ;
$timeout ( MatrixCall . getTurnServer , 60000 ) ;
} ) ;
}
2014-11-12 10:35:46 -05:00
// FIXME: we should prevent any calls from being placed or accepted before this has finished
2014-09-24 12:57:34 -04:00
MatrixCall . getTurnServer ( ) ;
2014-09-16 09:46:13 -04:00
MatrixCall . CALL _TIMEOUT = 60000 ;
2014-09-25 06:13:32 -04:00
MatrixCall . FALLBACK _STUN _SERVER = 'stun:stun.l.google.com:19302' ;
2014-09-16 09:46:13 -04:00
2014-09-11 10:23:06 -04:00
MatrixCall . prototype . createPeerConnection = function ( ) {
var pc ;
if ( window . mozRTCPeerConnection ) {
2014-09-24 11:07:33 -04:00
var iceServers = [ ] ;
if ( MatrixCall . turnServer ) {
2014-09-25 06:13:32 -04:00
if ( MatrixCall . turnServer . uris ) {
for ( var i = 0 ; i < MatrixCall . turnServer . uris . length ; i ++ ) {
iceServers . push ( {
'url' : MatrixCall . turnServer . uris [ i ] ,
'username' : MatrixCall . turnServer . username ,
'credential' : MatrixCall . turnServer . password ,
} ) ;
}
} else {
console . log ( "No TURN server: using fallback STUN server" ) ;
iceServers . push ( { 'url' : MatrixCall . FALLBACK _STUN _SERVER } ) ;
}
2014-09-24 11:07:33 -04:00
}
pc = new window . mozRTCPeerConnection ( { "iceServers" : iceServers } ) ;
2014-09-11 10:23:06 -04:00
} else {
2014-09-24 11:07:33 -04:00
var iceServers = [ ] ;
if ( MatrixCall . turnServer ) {
2014-09-25 06:13:32 -04:00
if ( MatrixCall . turnServer . uris ) {
iceServers . push ( {
'urls' : MatrixCall . turnServer . uris ,
'username' : MatrixCall . turnServer . username ,
'credential' : MatrixCall . turnServer . password ,
} ) ;
} else {
console . log ( "No TURN server: using fallback STUN server" ) ;
iceServers . push ( { 'urls' : MatrixCall . FALLBACK _STUN _SERVER } ) ;
}
2014-09-24 11:07:33 -04:00
}
pc = new window . RTCPeerConnection ( { "iceServers" : iceServers } ) ;
2014-09-11 10:23:06 -04:00
}
var self = this ;
pc . oniceconnectionstatechange = function ( ) { self . onIceConnectionStateChanged ( ) ; } ;
pc . onsignalingstatechange = function ( ) { self . onSignallingStateChanged ( ) ; } ;
pc . onicecandidate = function ( c ) { self . gotLocalIceCandidate ( c ) ; } ;
pc . onaddstream = function ( s ) { self . onAddStream ( s ) ; } ;
return pc ;
}
2014-09-17 11:26:35 -04:00
MatrixCall . prototype . getUserMediaVideoContraints = function ( callType ) {
switch ( callType ) {
case 'voice' :
return ( { audio : true , video : false } ) ;
case 'video' :
return ( { audio : true , video : {
mandatory : {
minWidth : 640 ,
maxWidth : 640 ,
minHeight : 360 ,
maxHeight : 360 ,
}
} } ) ;
}
} ;
MatrixCall . prototype . placeVoiceCall = function ( ) {
this . placeCallWithConstraints ( this . getUserMediaVideoContraints ( 'voice' ) ) ;
this . type = 'voice' ;
} ;
MatrixCall . prototype . placeVideoCall = function ( config ) {
this . placeCallWithConstraints ( this . getUserMediaVideoContraints ( 'video' ) ) ;
this . type = 'video' ;
} ;
MatrixCall . prototype . placeCallWithConstraints = function ( constraints ) {
2014-09-11 10:23:06 -04:00
var self = this ;
2014-08-27 13:57:54 -04:00
matrixPhoneService . callPlaced ( this ) ;
2014-09-17 11:26:35 -04:00
navigator . getUserMedia ( constraints , function ( s ) { self . gotUserMediaForInvite ( s ) ; } , function ( e ) { self . getUserMediaFailed ( e ) ; } ) ;
2014-09-09 12:37:50 -04:00
this . state = 'wait_local_media' ;
2014-09-05 19:14:02 -04:00
this . direction = 'outbound' ;
2014-09-17 11:26:35 -04:00
this . config = constraints ;
2014-08-27 13:57:54 -04:00
} ;
2014-09-16 10:25:51 -04:00
MatrixCall . prototype . initWithInvite = function ( event ) {
this . msg = event . content ;
2014-09-11 10:23:06 -04:00
this . peerConn = this . createPeerConnection ( ) ;
this . peerConn . setRemoteDescription ( new RTCSessionDescription ( this . msg . offer ) , this . onSetRemoteDescriptionSuccess , this . onSetRemoteDescriptionError ) ;
2014-08-28 14:03:34 -04:00
this . state = 'ringing' ;
2014-09-05 19:14:02 -04:00
this . direction = 'inbound' ;
2014-09-18 10:51:30 -04:00
2014-11-06 11:55:15 -05:00
// This also applied to the Safari OpenWebRTC extension so let's just do this all the time at least for now
//if (window.mozRTCPeerConnection) {
2014-09-18 10:51:30 -04:00
// firefox's RTCPeerConnection doesn't add streams until it starts getting media on them
// so we need to figure out whether a video channel has been offered by ourselves.
if ( this . msg . offer . sdp . indexOf ( 'm=video' ) > - 1 ) {
this . type = 'video' ;
} else {
this . type = 'voice' ;
}
2014-11-06 11:55:15 -05:00
//}
2014-09-18 10:51:30 -04:00
2014-09-16 10:25:51 -04:00
var self = this ;
$timeout ( function ( ) {
if ( self . state == 'ringing' ) {
self . state = 'ended' ;
self . hangupParty = 'remote' ; // effectively
self . stopAllMedia ( ) ;
if ( self . peerConn . signalingState != 'closed' ) self . peerConn . close ( ) ;
if ( self . onHangup ) self . onHangup ( self ) ;
}
} , this . msg . lifetime - event . age ) ;
2014-08-28 14:03:34 -04:00
} ;
2014-09-16 09:46:13 -04:00
// perverse as it may seem, sometimes we want to instantiate a call with a hangup message
// (because when getting the state of the room on load, events come in reverse order and
// we want to remember that a call has been hung up)
2014-09-16 10:25:51 -04:00
MatrixCall . prototype . initWithHangup = function ( event ) {
this . msg = event . content ;
2014-09-16 09:46:13 -04:00
this . state = 'ended' ;
} ;
2014-08-28 14:03:34 -04:00
MatrixCall . prototype . answer = function ( ) {
2014-09-11 10:23:06 -04:00
console . log ( "Answering call " + this . call _id ) ;
2014-09-19 12:21:17 -04:00
2014-09-11 10:23:06 -04:00
var self = this ;
2014-09-19 12:21:17 -04:00
2014-11-03 05:33:38 -05:00
var roomMembers = modelService . getRoom ( this . room _id ) . current _room _state . members ;
2014-11-06 09:52:22 -05:00
if ( roomMembers [ matrixService . config ( ) . user _id ] . event . content . membership != 'join' ) {
2014-09-19 12:21:17 -04:00
console . log ( "We need to join the room before we can accept this call" ) ;
matrixService . join ( this . room _id ) . then ( function ( ) {
self . answer ( ) ;
} , function ( ) {
console . log ( "Failed to join room: can't answer call!" ) ;
self . onError ( "Unable to join room to answer call!" ) ;
self . hangup ( ) ;
} ) ;
return ;
}
2014-09-11 10:23:06 -04:00
if ( ! this . localAVStream && ! this . waitForLocalAVStream ) {
2014-09-17 11:26:35 -04:00
navigator . getUserMedia ( this . getUserMediaVideoContraints ( this . type ) , function ( s ) { self . gotUserMediaForAnswer ( s ) ; } , function ( e ) { self . getUserMediaFailed ( e ) ; } ) ;
2014-09-11 10:23:06 -04:00
this . state = 'wait_local_media' ;
} else if ( this . localAVStream ) {
this . gotUserMediaForAnswer ( this . localAVStream ) ;
} else if ( this . waitForLocalAVStream ) {
this . state = 'wait_local_media' ;
}
2014-08-28 14:03:34 -04:00
} ;
2014-08-29 10:18:37 -04:00
MatrixCall . prototype . stopAllMedia = function ( ) {
2014-08-29 08:28:04 -04:00
if ( this . localAVStream ) {
forAllTracksOnStream ( this . localAVStream , function ( t ) {
2014-09-09 09:53:47 -04:00
if ( t . stop ) t . stop ( ) ;
2014-08-29 08:28:04 -04:00
} ) ;
}
if ( this . remoteAVStream ) {
forAllTracksOnStream ( this . remoteAVStream , function ( t ) {
2014-09-09 09:53:47 -04:00
if ( t . stop ) t . stop ( ) ;
2014-08-29 08:28:04 -04:00
} ) ;
}
2014-08-29 10:18:37 -04:00
} ;
2014-09-22 06:44:15 -04:00
MatrixCall . prototype . hangup = function ( reason , suppressEvent ) {
2014-09-11 10:23:06 -04:00
console . log ( "Ending call " + this . call _id ) ;
2014-08-29 10:18:37 -04:00
2014-09-18 10:51:30 -04:00
// pausing now keeps the last frame (ish) of the video call in the video element
// rather than it just turning black straight away
2014-11-07 12:56:28 -05:00
if ( this . getRemoteVideoElement ( ) && this . getRemoteVideoElement ( ) . pause ) this . getRemoteVideoElement ( ) . pause ( ) ;
if ( this . getLocalVideoElement ( ) && this . getLocalVideoElement ( ) . pause ) this . getLocalVideoElement ( ) . pause ( ) ;
2014-09-18 06:04:45 -04:00
2014-08-29 10:18:37 -04:00
this . stopAllMedia ( ) ;
2014-09-09 12:52:01 -04:00
if ( this . peerConn ) this . peerConn . close ( ) ;
2014-08-29 06:29:36 -04:00
2014-09-09 12:37:50 -04:00
this . hangupParty = 'local' ;
2014-09-22 06:44:15 -04:00
this . hangupReason = reason ;
2014-09-09 12:37:50 -04:00
2014-08-28 14:03:34 -04:00
var content = {
version : 0 ,
call _id : this . call _id ,
2014-09-22 06:44:15 -04:00
reason : reason
2014-08-28 14:03:34 -04:00
} ;
2014-09-12 11:31:56 -04:00
this . sendEventWithRetry ( 'm.call.hangup' , content ) ;
2014-08-28 14:03:34 -04:00
this . state = 'ended' ;
2014-09-11 10:23:06 -04:00
if ( this . onHangup && ! suppressEvent ) this . onHangup ( this ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . gotUserMediaForInvite = function ( stream ) {
2014-09-11 10:23:06 -04:00
if ( this . successor ) {
this . successor . gotUserMediaForAnswer ( stream ) ;
return ;
}
if ( this . state == 'ended' ) return ;
2014-09-09 12:58:26 -04:00
2014-11-07 12:56:28 -05:00
var videoEl = this . getLocalVideoElement ( ) ;
if ( videoEl && this . type == 'video' ) {
2014-09-17 11:26:35 -04:00
var vidTrack = stream . getVideoTracks ( ) [ 0 ] ;
2014-11-07 12:56:28 -05:00
videoEl . autoplay = true ;
videoEl . src = URL . createObjectURL ( stream ) ;
videoEl . muted = true ;
var self = this ;
$timeout ( function ( ) {
var vel = self . getLocalVideoElement ( ) ;
if ( vel . play ) vel . play ( ) ;
} ) ;
2014-09-17 11:26:35 -04:00
}
2014-08-29 06:29:36 -04:00
this . localAVStream = stream ;
2014-08-28 14:03:34 -04:00
var audioTracks = stream . getAudioTracks ( ) ;
for ( var i = 0 ; i < audioTracks . length ; i ++ ) {
audioTracks [ i ] . enabled = true ;
}
2014-09-11 10:23:06 -04:00
this . peerConn = this . createPeerConnection ( ) ;
2014-09-11 13:59:22 -04:00
this . peerConn . addStream ( stream ) ;
2014-09-11 10:23:06 -04:00
var self = this ;
2014-08-27 13:57:54 -04:00
this . peerConn . createOffer ( function ( d ) {
self . gotLocalOffer ( d ) ;
} , function ( e ) {
self . getLocalOfferFailed ( e ) ;
} ) ;
2014-09-08 11:10:36 -04:00
$rootScope . $apply ( function ( ) {
self . state = 'create_offer' ;
} ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . gotUserMediaForAnswer = function ( stream ) {
2014-09-11 10:23:06 -04:00
if ( this . state == 'ended' ) return ;
2014-09-09 12:58:26 -04:00
2014-11-07 12:56:28 -05:00
var localVidEl = this . getLocalVideoElement ( ) ;
if ( localVidEl && this . type == 'video' ) {
localVidEl . autoplay = true ;
2014-09-17 11:26:35 -04:00
var vidTrack = stream . getVideoTracks ( ) [ 0 ] ;
2014-11-07 12:56:28 -05:00
localVidEl . src = URL . createObjectURL ( stream ) ;
localVidEl . muted = true ;
var self = this ;
$timeout ( function ( ) {
var vel = self . getLocalVideoElement ( ) ;
if ( vel . play ) vel . play ( ) ;
} ) ;
2014-09-17 11:26:35 -04:00
}
2014-08-29 06:29:36 -04:00
this . localAVStream = stream ;
2014-08-28 14:03:34 -04:00
var audioTracks = stream . getAudioTracks ( ) ;
for ( var i = 0 ; i < audioTracks . length ; i ++ ) {
audioTracks [ i ] . enabled = true ;
}
this . peerConn . addStream ( stream ) ;
2014-09-11 10:23:06 -04:00
var self = this ;
2014-08-28 14:03:34 -04:00
var constraints = {
'mandatory' : {
'OfferToReceiveAudio' : true ,
2014-09-17 11:26:35 -04:00
'OfferToReceiveVideo' : this . type == 'video'
2014-08-28 14:03:34 -04:00
} ,
} ;
this . peerConn . createAnswer ( function ( d ) { self . createdAnswer ( d ) ; } , function ( e ) { } , constraints ) ;
2014-09-11 14:16:57 -04:00
// This can't be in an apply() because it's called by a predecessor call under glare conditions :(
self . state = 'create_answer' ;
2014-08-27 13:57:54 -04:00
} ;
MatrixCall . prototype . gotLocalIceCandidate = function ( event ) {
if ( event . candidate ) {
2014-09-22 05:54:14 -04:00
console . log ( "Got local ICE " + event . candidate . sdpMid + " candidate: " + event . candidate . candidate ) ;
2014-09-12 13:16:24 -04:00
this . sendCandidate ( event . candidate ) ;
2014-08-27 13:57:54 -04:00
}
}
MatrixCall . prototype . gotRemoteIceCandidate = function ( cand ) {
2014-09-10 06:12:02 -04:00
if ( this . state == 'ended' ) {
2014-11-07 12:56:28 -05:00
//console.log("Ignoring remote ICE candidate because call has ended");
2014-09-10 06:12:02 -04:00
return ;
}
2014-11-07 12:56:28 -05:00
console . log ( "Got remote ICE " + cand . sdpMid + " candidate: " + cand . candidate ) ;
2014-09-16 09:46:13 -04:00
this . peerConn . addIceCandidate ( new RTCIceCandidate ( cand ) , function ( ) { } , function ( e ) { } ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . receivedAnswer = function ( msg ) {
2014-09-16 09:46:13 -04:00
if ( this . state == 'ended' ) return ;
2014-09-11 10:23:06 -04:00
this . peerConn . setRemoteDescription ( new RTCSessionDescription ( msg . answer ) , this . onSetRemoteDescriptionSuccess , this . onSetRemoteDescriptionError ) ;
2014-08-28 14:03:34 -04:00
this . state = 'connecting' ;
2014-08-27 13:57:54 -04:00
} ;
2014-09-17 11:26:35 -04:00
2014-08-27 13:57:54 -04:00
MatrixCall . prototype . gotLocalOffer = function ( description ) {
2014-09-11 10:23:06 -04:00
console . log ( "Created offer: " + description ) ;
2014-09-12 09:06:35 -04:00
if ( this . state == 'ended' ) {
console . log ( "Ignoring newly created offer on call ID " + this . call _id + " because the call has ended" ) ;
return ;
}
2014-09-11 10:23:06 -04:00
var self = this ;
2014-11-07 12:56:28 -05:00
this . peerConn . setLocalDescription ( description , function ( ) {
var content = {
version : 0 ,
call _id : self . call _id ,
// OpenWebRTC appears to add extra stuff (like the DTLS fingerprint) to the description
// when setting it on the peerconnection. According to the spec it should only add ICE
// candidates. Any ICE candidates that have already been generated at this point will
// probably be sent both in the offer and separately. Ho hum.
offer : self . peerConn . localDescription ,
lifetime : MatrixCall . CALL _TIMEOUT
} ;
self . sendEventWithRetry ( 'm.call.invite' , content ) ;
$timeout ( function ( ) {
if ( self . state == 'invite_sent' ) {
self . hangup ( 'invite_timeout' ) ;
}
} , MatrixCall . CALL _TIMEOUT ) ;
2014-09-16 09:46:13 -04:00
2014-11-07 12:56:28 -05:00
$rootScope . $apply ( function ( ) {
self . state = 'invite_sent' ;
} ) ;
} , function ( ) { console . log ( "Error setting local description!" ) ; } ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . createdAnswer = function ( description ) {
2014-09-11 10:23:06 -04:00
console . log ( "Created answer: " + description ) ;
var self = this ;
2014-11-07 12:56:28 -05:00
this . peerConn . setLocalDescription ( description , function ( ) {
var content = {
version : 0 ,
call _id : self . call _id ,
answer : self . peerConn . localDescription
} ;
self . sendEventWithRetry ( 'm.call.answer' , content ) ;
$rootScope . $apply ( function ( ) {
self . state = 'connecting' ;
} ) ;
} , function ( ) { console . log ( "Error setting local description!" ) ; } ) ;
2014-08-27 13:57:54 -04:00
} ;
MatrixCall . prototype . getLocalOfferFailed = function ( error ) {
this . onError ( "Failed to start audio for call!" ) ;
} ;
MatrixCall . prototype . getUserMediaFailed = function ( ) {
2014-09-18 10:51:30 -04:00
this . onError ( "Couldn't start capturing! Is your microphone set up?" ) ;
2014-09-09 13:21:03 -04:00
this . hangup ( ) ;
2014-08-27 13:57:54 -04:00
} ;
2014-08-28 14:03:34 -04:00
MatrixCall . prototype . onIceConnectionStateChanged = function ( ) {
2014-09-05 19:14:02 -04:00
if ( this . state == 'ended' ) return ; // because ICE can still complete as we're ending the call
2014-09-11 10:23:06 -04:00
console . log ( "Ice connection state changed to: " + this . peerConn . iceConnectionState ) ;
2014-08-29 06: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-11 10:23:06 -04:00
var self = this ;
2014-09-08 11:10:36 -04:00
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
self . didConnect = true ;
} ) ;
2014-09-22 05:54:14 -04:00
} else if ( this . peerConn . iceConnectionState == 'failed' ) {
2014-09-22 06:44:15 -04:00
this . hangup ( 'ice_failed' ) ;
2014-08-28 14:03:34 -04:00
}
} ;
MatrixCall . prototype . onSignallingStateChanged = function ( ) {
2014-09-11 10:23:06 -04:00
console . log ( "call " + this . call _id + ": Signalling state changed to: " + this . peerConn . signalingState ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . onSetRemoteDescriptionSuccess = function ( ) {
2014-09-11 10:23:06 -04:00
console . log ( "Set remote description" ) ;
2014-08-28 14:03:34 -04:00
} ;
2014-08-27 13:57:54 -04:00
2014-08-28 14:03:34 -04:00
MatrixCall . prototype . onSetRemoteDescriptionError = function ( e ) {
2014-09-11 10:23:06 -04:00
console . log ( "Failed to set remote description" + e ) ;
2014-08-28 14:03:34 -04:00
} ;
MatrixCall . prototype . onAddStream = function ( event ) {
2014-09-11 10:23:06 -04:00
console . log ( "Stream added" + event ) ;
2014-08-29 06:29:36 -04:00
var s = event . stream ;
this . remoteAVStream = s ;
2014-09-17 11:26:35 -04:00
if ( this . direction == 'inbound' ) {
if ( s . getVideoTracks ( ) . length > 0 ) {
this . type = 'video' ;
} else {
this . type = 'voice' ;
}
}
2014-08-29 06:29:36 -04:00
var self = this ;
forAllTracksOnStream ( s , function ( t ) {
// not currently implemented in chrome
t . onstarted = self . onRemoteStreamTrackStarted ;
} ) ;
2014-08-29 10:18:37 -04:00
event . stream . onended = function ( e ) { self . onRemoteStreamEnded ( e ) ; } ;
2014-08-29 06:29:36 -04:00
// not currently implemented in chrome
2014-08-29 10:18:37 -04:00
event . stream . onstarted = function ( e ) { self . onRemoteStreamStarted ( e ) ; } ;
2014-09-17 11:26:35 -04:00
this . tryPlayRemoteStream ( ) ;
} ;
MatrixCall . prototype . tryPlayRemoteStream = function ( event ) {
2014-11-07 12:56:28 -05:00
if ( this . getRemoteVideoElement ( ) && this . remoteAVStream ) {
var player = this . getRemoteVideoElement ( ) ;
player . autoplay = true ;
2014-09-17 11:26:35 -04:00
player . src = URL . createObjectURL ( this . remoteAVStream ) ;
2014-11-07 12:56:28 -05:00
var self = this ;
$timeout ( function ( ) {
var vel = self . getRemoteVideoElement ( ) ;
if ( vel . play ) vel . play ( ) ;
} ) ;
2014-09-17 11:26:35 -04:00
}
2014-08-28 14:03:34 -04:00
} ;
2014-08-29 06:29:36 -04:00
MatrixCall . prototype . onRemoteStreamStarted = function ( event ) {
2014-09-11 10:23:06 -04:00
var self = this ;
2014-09-08 11:10:36 -04:00
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
} ) ;
2014-08-29 06:29:36 -04:00
} ;
2014-08-29 10:18:37 -04:00
MatrixCall . prototype . onRemoteStreamEnded = function ( event ) {
2014-09-11 10:23:06 -04:00
console . log ( "Remote stream ended" ) ;
var self = this ;
2014-09-08 11:10:36 -04:00
$rootScope . $apply ( function ( ) {
self . state = 'ended' ;
2014-09-10 06:12:02 -04:00
self . hangupParty = 'remote' ;
2014-09-08 11:10:36 -04:00
self . stopAllMedia ( ) ;
2014-09-10 06:12:02 -04:00
if ( self . peerConn . signalingState != 'closed' ) self . peerConn . close ( ) ;
if ( self . onHangup ) self . onHangup ( self ) ;
2014-09-08 11:10:36 -04:00
} ) ;
2014-08-29 10:18:37 -04:00
} ;
2014-08-29 06:29:36 -04:00
MatrixCall . prototype . onRemoteStreamTrackStarted = function ( event ) {
2014-09-11 10:23:06 -04:00
var self = this ;
2014-09-08 11:10:36 -04:00
$rootScope . $apply ( function ( ) {
self . state = 'connected' ;
} ) ;
2014-08-29 06:29:36 -04:00
} ;
2014-09-22 06:44:15 -04:00
MatrixCall . prototype . onHangupReceived = function ( msg ) {
2014-09-11 10:23:06 -04:00
console . log ( "Hangup received" ) ;
2014-11-07 12:56:28 -05:00
if ( this . getRemoteVideoElement ( ) && this . getRemoteVideoElement ( ) . pause ) this . getRemoteVideoElement ( ) . pause ( ) ;
if ( this . getLocalVideoElement ( ) && this . getLocalVideoElement ( ) . pause ) this . getLocalVideoElement ( ) . pause ( ) ;
2014-08-29 06:29:36 -04:00
this . state = 'ended' ;
2014-09-09 12:37:50 -04:00
this . hangupParty = 'remote' ;
2014-09-22 06:44:15 -04:00
this . hangupReason = msg . reason ;
2014-08-29 10:18:37 -04:00
this . stopAllMedia ( ) ;
2014-09-17 11:26:35 -04:00
if ( this . peerConn && this . peerConn . signalingState != 'closed' ) this . peerConn . close ( ) ;
2014-09-11 10:23:06 -04:00
if ( this . onHangup ) this . onHangup ( this ) ;
} ;
MatrixCall . prototype . replacedBy = function ( newCall ) {
2014-09-12 06:51:57 -04:00
console . log ( this . call _id + " being replaced by " + newCall . call _id ) ;
2014-09-11 10:23:06 -04:00
if ( this . state == 'wait_local_media' ) {
2014-09-12 06:51:57 -04:00
console . log ( "Telling new call to wait for local media" ) ;
2014-09-11 10:23:06 -04:00
newCall . waitForLocalAVStream = true ;
} else if ( this . state == 'create_offer' ) {
2014-09-12 06:51:57 -04:00
console . log ( "Handing local stream to new call" ) ;
2014-09-17 11:26:35 -04:00
newCall . gotUserMediaForAnswer ( this . localAVStream ) ;
2014-09-12 06:51:57 -04:00
delete ( this . localAVStream ) ;
2014-09-11 10:23:06 -04:00
} else if ( this . state == 'invite_sent' ) {
2014-09-12 06:51:57 -04:00
console . log ( "Handing local stream to new call" ) ;
2014-09-17 11:26:35 -04:00
newCall . gotUserMediaForAnswer ( this . localAVStream ) ;
2014-09-12 06:51:57 -04:00
delete ( this . localAVStream ) ;
2014-09-11 10:23:06 -04:00
}
2014-11-07 12:56:28 -05:00
newCall . localVideoSelector = this . localVideoSelector ;
newCall . remoteVideoSelector = this . remoteVideoSelector ;
2014-09-11 10:23:06 -04:00
this . successor = newCall ;
this . hangup ( true ) ;
2014-08-29 06:29:36 -04:00
} ;
2014-09-12 11:31:56 -04:00
MatrixCall . prototype . sendEventWithRetry = function ( evType , content ) {
var ev = { type : evType , content : content , tries : 1 } ;
var self = this ;
matrixService . sendEvent ( this . room _id , evType , undefined , content ) . then ( this . eventSent , function ( error ) { self . eventSendFailed ( ev , error ) ; } ) ;
} ;
MatrixCall . prototype . eventSent = function ( ) {
} ;
MatrixCall . prototype . eventSendFailed = function ( ev , error ) {
if ( ev . tries > 5 ) {
console . log ( "Failed to send event of type " + ev . type + " on attempt " + ev . tries + ". Giving up." ) ;
return ;
}
var delayMs = 500 * Math . pow ( 2 , ev . tries ) ;
console . log ( "Failed to send event of type " + ev . type + ". Retrying in " + delayMs + "ms" ) ;
++ ev . tries ;
var self = this ;
$timeout ( function ( ) {
matrixService . sendEvent ( self . room _id , ev . type , undefined , ev . content ) . then ( self . eventSent , function ( error ) { self . eventSendFailed ( ev , error ) ; } ) ;
} , delayMs ) ;
} ;
2014-09-12 13:16:24 -04:00
// Sends candidates with are sent in a special way because we try to amalgamate them into one message
MatrixCall . prototype . sendCandidate = function ( content ) {
this . candidateSendQueue . push ( content ) ;
var self = this ;
if ( this . candidateSendTries == 0 ) $timeout ( function ( ) { self . sendCandidateQueue ( ) ; } , 100 ) ;
} ;
MatrixCall . prototype . sendCandidateQueue = function ( content ) {
if ( this . candidateSendQueue . length == 0 ) return ;
var cands = this . candidateSendQueue ;
this . candidateSendQueue = [ ] ;
++ this . candidateSendTries ;
var content = {
version : 0 ,
call _id : this . call _id ,
candidates : cands
} ;
var self = this ;
console . log ( "Attempting to send " + cands . length + " candidates" ) ;
matrixService . sendEvent ( self . room _id , 'm.call.candidates' , undefined , content ) . then ( function ( ) { self . candsSent ( ) ; } , function ( error ) { self . candsSendFailed ( cands , error ) ; } ) ;
} ;
MatrixCall . prototype . candsSent = function ( ) {
this . candidateSendTries = 0 ;
this . sendCandidateQueue ( ) ;
} ;
MatrixCall . prototype . candsSendFailed = function ( cands , error ) {
for ( var i = 0 ; i < cands . length ; ++ i ) {
this . candidateSendQueue . push ( cands [ i ] ) ;
}
if ( this . candidateSendTries > 5 ) {
2014-09-19 06:41:49 -04:00
console . log ( "Failed to send candidates on attempt " + this . candidateSendTries + ". Giving up for now." ) ;
2014-09-12 13:16:24 -04:00
this . candidateSendTries = 0 ;
return ;
}
var delayMs = 500 * Math . pow ( 2 , this . candidateSendTries ) ;
++ this . candidateSendTries ;
console . log ( "Failed to send candidates. Retrying in " + delayMs + "ms" ) ;
var self = this ;
$timeout ( function ( ) {
self . sendCandidateQueue ( ) ;
} , delayMs ) ;
} ;
2014-11-07 12:56:28 -05:00
MatrixCall . prototype . getLocalVideoElement = function ( ) {
if ( this . localVideoSelector ) {
var t = angular . element ( this . localVideoSelector ) ;
if ( t . length ) return t [ 0 ] ;
}
return null ;
} ;
MatrixCall . prototype . getRemoteVideoElement = function ( ) {
if ( this . remoteVideoSelector ) {
var t = angular . element ( this . remoteVideoSelector ) ;
if ( t . length ) return t [ 0 ] ;
}
return null ;
} ;
2014-08-27 13:57:54 -04:00
return MatrixCall ;
} ] ) ;