2014-06-21 05:26:06 +05:00
/ *
* Verto HTML5 / Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
* Copyright ( C ) 2005 - 2014 , Anthony Minessale II < anthm @ freeswitch . org >
*
* Version : MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 ( 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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis ,
* WITHOUT WARRANTY OF ANY KIND , either express or implied . See the License
* for the specific language governing rights and limitations under the
* License .
*
* The Original Code is Verto HTML5 / Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
*
* The Initial Developer of the Original Code is
* Anthony Minessale II < anthm @ freeswitch . org >
* Portions created by the Initial Developer are Copyright ( C )
* the Initial Developer . All Rights Reserved .
*
* Contributor ( s ) :
*
* Anthony Minessale II < anthm @ freeswitch . org >
*
* jquery . FSRTC . js - WebRTC Glue code
*
* /
( function ( $ ) {
// Find the line in sdpLines that starts with |prefix|, and, if specified,
// contains |substr| (case-insensitive search).
function findLine ( sdpLines , prefix , substr ) {
return findLineInRange ( sdpLines , 0 , - 1 , prefix , substr ) ;
}
// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
// and, if specified, contains |substr| (case-insensitive search).
function findLineInRange ( sdpLines , startLine , endLine , prefix , substr ) {
var realEndLine = ( endLine != - 1 ) ? endLine : sdpLines . length ;
for ( var i = startLine ; i < realEndLine ; ++ i ) {
if ( sdpLines [ i ] . indexOf ( prefix ) === 0 ) {
if ( ! substr || sdpLines [ i ] . toLowerCase ( ) . indexOf ( substr . toLowerCase ( ) ) !== - 1 ) {
return i ;
}
}
}
return null ;
}
// Gets the codec payload type from an a=rtpmap:X line.
function getCodecPayloadType ( sdpLine ) {
var pattern = new RegExp ( 'a=rtpmap:(\\d+) \\w+\\/\\d+' ) ;
var result = sdpLine . match ( pattern ) ;
return ( result && result . length == 2 ) ? result [ 1 ] : null ;
}
2015-08-28 14:11:03 -05:00
2014-06-21 05:26:06 +05:00
// Returns a new m= line with the specified codec as the first one.
function setDefaultCodec ( mLine , payload ) {
var elements = mLine . split ( ' ' ) ;
var newLine = [ ] ;
var index = 0 ;
for ( var i = 0 ; i < elements . length ; i ++ ) {
2014-11-11 23:33:03 +08:00
if ( index === 3 ) { // Format of media starts from the fourth.
newLine [ index ++ ] = payload ; // Put target payload to the first.
}
2014-06-21 05:26:06 +05:00
if ( elements [ i ] !== payload ) newLine [ index ++ ] = elements [ i ] ;
}
return newLine . join ( ' ' ) ;
}
$ . FSRTC = function ( options ) {
this . options = $ . extend ( {
useVideo : null ,
useStereo : false ,
userData : null ,
2015-03-24 13:39:51 -05:00
localVideo : null ,
screenShare : false ,
useCamera : "any" ,
2014-11-11 23:33:03 +08:00
iceServers : false ,
2014-06-21 05:26:06 +05:00
videoParams : { } ,
2014-10-13 19:26:20 -04:00
audioParams : { } ,
2014-06-21 05:26:06 +05:00
callbacks : {
onICEComplete : function ( ) { } ,
onICE : function ( ) { } ,
onOfferSDP : function ( ) { }
2015-03-24 13:39:51 -05:00
} ,
2018-07-26 17:03:24 -05:00
useStream : null ,
2014-11-11 23:33:03 +08:00
} , options ) ;
2014-06-21 05:26:06 +05:00
2015-11-16 11:31:27 -06:00
this . audioEnabled = true ;
this . videoEnabled = true ;
2015-03-24 13:39:51 -05:00
2015-08-28 09:42:37 -05:00
2014-06-21 05:26:06 +05:00
this . mediaData = {
SDP : null ,
profile : { } ,
candidateList : [ ]
} ;
2016-06-08 16:20:18 -05:00
this . constraints = {
offerToReceiveAudio : this . options . useSpeak === "none" ? false : true ,
offerToReceiveVideo : this . options . useVideo ? true : false ,
} ;
2014-06-21 05:26:06 +05:00
if ( self . options . useVideo ) {
self . options . useVideo . style . display = 'none' ;
}
setCompat ( ) ;
checkCompat ( ) ;
} ;
2015-08-28 14:11:03 -05:00
$ . FSRTC . validRes = [ ] ;
2015-03-24 13:39:51 -05:00
$ . FSRTC . prototype . useVideo = function ( obj , local ) {
2014-06-21 05:26:06 +05:00
var self = this ;
if ( obj ) {
self . options . useVideo = obj ;
2015-03-24 13:39:51 -05:00
self . options . localVideo = local ;
2016-06-08 16:20:18 -05:00
self . constraints . offerToReceiveVideo = true ;
2014-06-21 05:26:06 +05:00
} else {
self . options . useVideo = null ;
2015-03-24 13:39:51 -05:00
self . options . localVideo = null ;
2016-06-08 16:20:18 -05:00
self . constraints . offerToReceiveVideo = false ;
2014-06-21 05:26:06 +05:00
}
if ( self . options . useVideo ) {
self . options . useVideo . style . display = 'none' ;
}
} ;
$ . FSRTC . prototype . useStereo = function ( on ) {
var self = this ;
self . options . useStereo = on ;
} ;
// Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
$ . FSRTC . prototype . stereoHack = function ( sdp ) {
var self = this ;
if ( ! self . options . useStereo ) {
return sdp ;
}
var sdpLines = sdp . split ( '\r\n' ) ;
// Find opus payload.
2015-09-22 15:10:29 -05:00
var opusIndex = findLine ( sdpLines , 'a=rtpmap' , 'opus/48000' ) , opusPayload ;
if ( ! opusIndex ) {
return sdp ;
} else {
2014-06-21 05:26:06 +05:00
opusPayload = getCodecPayloadType ( sdpLines [ opusIndex ] ) ;
}
// Find the payload in fmtp line.
var fmtpLineIndex = findLine ( sdpLines , 'a=fmtp:' + opusPayload . toString ( ) ) ;
2015-09-22 15:10:29 -05:00
if ( fmtpLineIndex === null ) {
// create an fmtp line
2015-09-22 15:31:09 -05:00
sdpLines [ opusIndex ] = sdpLines [ opusIndex ] + '\r\na=fmtp:' + opusPayload . toString ( ) + " stereo=1; sprop-stereo=1"
2015-09-22 15:10:29 -05:00
} else {
// Append stereo=1 to fmtp line.
2015-09-22 15:31:09 -05:00
sdpLines [ fmtpLineIndex ] = sdpLines [ fmtpLineIndex ] . concat ( '; stereo=1; sprop-stereo=1' ) ;
2015-09-22 15:10:29 -05:00
}
2014-06-21 05:26:06 +05:00
sdp = sdpLines . join ( '\r\n' ) ;
return sdp ;
} ;
function setCompat ( ) {
}
function checkCompat ( ) {
return true ;
}
2014-12-06 11:40:44 -06:00
function onStreamError ( self , e ) {
2015-03-24 12:49:06 -05:00
console . log ( 'There has been a problem retrieving the streams - did you allow access? Check Device Resolution' , e ) ;
doCallback ( self , "onError" , e ) ;
2014-06-21 05:26:06 +05:00
}
2015-03-24 13:39:51 -05:00
function onStreamSuccess ( self , stream ) {
2014-06-21 05:26:06 +05:00
console . log ( "Stream Success" ) ;
2015-03-24 13:39:51 -05:00
doCallback ( self , "onStream" , stream ) ;
2014-06-21 05:26:06 +05:00
}
2018-07-26 15:34:53 -05:00
function onRemoteStreamSuccess ( self , stream ) {
console . log ( "Remote Stream Success" ) ;
doCallback ( self , "onRemoteStream" , stream ) ;
}
2014-06-21 05:26:06 +05:00
function onICE ( self , candidate ) {
self . mediaData . candidate = candidate ;
self . mediaData . candidateList . push ( self . mediaData . candidate ) ;
doCallback ( self , "onICE" ) ;
}
function doCallback ( self , func , arg ) {
if ( func in self . options . callbacks ) {
self . options . callbacks [ func ] ( self , arg ) ;
}
}
function onICEComplete ( self , candidate ) {
console . log ( "ICE Complete" ) ;
doCallback ( self , "onICEComplete" ) ;
}
function onChannelError ( self , e ) {
console . error ( "Channel Error" , e ) ;
doCallback ( self , "onError" , e ) ;
}
function onICESDP ( self , sdp ) {
self . mediaData . SDP = self . stereoHack ( sdp . sdp ) ;
console . log ( "ICE SDP" ) ;
doCallback ( self , "onICESDP" ) ;
}
function onAnswerSDP ( self , sdp ) {
self . answer . SDP = self . stereoHack ( sdp . sdp ) ;
console . log ( "ICE ANSWER SDP" ) ;
doCallback ( self , "onAnswerSDP" , self . answer . SDP ) ;
}
function onMessage ( self , msg ) {
console . log ( "Message" ) ;
doCallback ( self , "onICESDP" , msg ) ;
}
2016-06-08 16:20:18 -05:00
FSRTCattachMediaStream = function ( element , stream ) {
2017-06-22 16:43:11 -05:00
if ( typeof element . srcObject !== 'undefined' ) {
element . srcObject = stream ;
2016-06-08 16:20:18 -05:00
} else {
2017-06-22 16:43:11 -05:00
console . error ( 'Error attaching stream to element.' ) ;
2016-06-08 16:20:18 -05:00
}
}
2014-06-21 05:26:06 +05:00
function onRemoteStream ( self , stream ) {
if ( self . options . useVideo ) {
self . options . useVideo . style . display = 'block' ;
2017-09-20 09:15:56 -05:00
// Hacks for Mobile Safari
2017-09-20 18:00:39 -05:00
var iOS = [ 'iPad' , 'iPhone' , 'iPod' ] . indexOf ( navigator . platform ) >= 0 ;
2017-09-20 09:15:56 -05:00
2017-09-20 18:00:39 -05:00
if ( iOS ) {
self . options . useVideo . setAttribute ( "playsinline" , true ) ;
}
2014-06-21 05:26:06 +05:00
}
var element = self . options . useAudio ;
console . log ( "REMOTE STREAM" , stream , element ) ;
2016-06-08 16:20:18 -05:00
FSRTCattachMediaStream ( element , stream ) ;
2017-09-20 09:15:56 -05:00
//self.options.useAudio.play();
2014-06-21 05:26:06 +05:00
self . remoteStream = stream ;
2018-07-26 15:34:53 -05:00
onRemoteStreamSuccess ( self , stream ) ;
2014-06-21 05:26:06 +05:00
}
function onOfferSDP ( self , sdp ) {
self . mediaData . SDP = self . stereoHack ( sdp . sdp ) ;
console . log ( "Offer SDP" ) ;
doCallback ( self , "onOfferSDP" ) ;
}
$ . FSRTC . prototype . answer = function ( sdp , onSuccess , onError ) {
this . peer . addAnswerSDP ( {
type : "answer" ,
sdp : sdp
} ,
onSuccess , onError ) ;
} ;
2015-09-22 11:21:15 -05:00
$ . FSRTC . prototype . stopPeer = function ( ) {
if ( self . peer ) {
console . log ( "stopping peer" ) ;
self . peer . stop ( ) ;
}
}
2014-06-21 05:26:06 +05:00
$ . FSRTC . prototype . stop = function ( ) {
var self = this ;
if ( self . options . useVideo ) {
self . options . useVideo . style . display = 'none' ;
2016-06-08 16:20:18 -05:00
self . options . useVideo [ 'src' ] = '' ;
2014-06-21 05:26:06 +05:00
}
2018-07-26 17:03:24 -05:00
if ( self . localStream && ! self . options . useStream ) {
2015-09-25 10:30:35 -03:00
if ( typeof self . localStream . stop == 'function' ) {
self . localStream . stop ( ) ;
} else {
2015-09-29 11:46:15 -05:00
if ( self . localStream . active ) {
var tracks = self . localStream . getTracks ( ) ;
2016-10-11 15:51:32 -07:00
console . log ( tracks ) ;
2015-09-29 11:46:15 -05:00
tracks . forEach ( function ( track , index ) {
console . log ( track ) ;
track . stop ( ) ;
} )
}
2015-09-25 10:30:35 -03:00
}
2014-06-21 05:26:06 +05:00
self . localStream = null ;
}
2015-03-24 13:39:51 -05:00
if ( self . options . localVideo ) {
2018-07-26 17:03:24 -05:00
deactivateLocalVideo ( self . options . localVideo ) ;
2015-03-24 13:39:51 -05:00
}
2018-07-26 17:03:24 -05:00
if ( self . options . localVideoStream && ! self . options . useStream ) {
2015-09-25 10:30:35 -03:00
if ( typeof self . options . localVideoStream . stop == 'function' ) {
self . options . localVideoStream . stop ( ) ;
} else {
2016-01-28 16:30:25 -06:00
if ( self . options . localVideoStream . active ) {
var tracks = self . options . localVideoStream . getTracks ( ) ;
2016-10-11 15:51:32 -07:00
console . log ( tracks ) ;
2015-09-29 11:46:15 -05:00
tracks . forEach ( function ( track , index ) {
console . log ( track ) ;
track . stop ( ) ;
} )
}
2015-09-25 10:30:35 -03:00
}
2015-03-24 13:39:51 -05:00
}
2014-06-21 05:26:06 +05:00
if ( self . peer ) {
console . log ( "stopping peer" ) ;
self . peer . stop ( ) ;
}
} ;
2015-03-24 13:39:51 -05:00
$ . FSRTC . prototype . getMute = function ( ) {
var self = this ;
2015-11-16 11:31:27 -06:00
return self . audioEnabled ;
2015-03-24 13:39:51 -05:00
}
$ . FSRTC . prototype . setMute = function ( what ) {
var self = this ;
2018-04-21 18:11:12 -03:00
if ( ! self . localStream ) {
return false ;
}
2015-03-24 13:39:51 -05:00
var audioTracks = self . localStream . getAudioTracks ( ) ;
for ( var i = 0 , len = audioTracks . length ; i < len ; i ++ ) {
switch ( what ) {
case "on" :
audioTracks [ i ] . enabled = true ;
break ;
case "off" :
audioTracks [ i ] . enabled = false ;
break ;
case "toggle" :
audioTracks [ i ] . enabled = ! audioTracks [ i ] . enabled ;
default :
break ;
}
2015-11-16 11:31:27 -06:00
self . audioEnabled = audioTracks [ i ] . enabled ;
2015-03-24 13:39:51 -05:00
}
2015-11-16 11:31:27 -06:00
return ! self . audioEnabled ;
}
$ . FSRTC . prototype . getVideoMute = function ( ) {
var self = this ;
return self . videoEnabled ;
}
$ . FSRTC . prototype . setVideoMute = function ( what ) {
var self = this ;
2018-04-21 18:11:12 -03:00
if ( ! self . localStream ) {
return false ;
}
2015-11-16 11:31:27 -06:00
var videoTracks = self . localStream . getVideoTracks ( ) ;
for ( var i = 0 , len = videoTracks . length ; i < len ; i ++ ) {
switch ( what ) {
case "on" :
videoTracks [ i ] . enabled = true ;
break ;
case "off" :
videoTracks [ i ] . enabled = false ;
break ;
case "toggle" :
videoTracks [ i ] . enabled = ! videoTracks [ i ] . enabled ;
default :
break ;
}
self . videoEnabled = videoTracks [ i ] . enabled ;
}
return ! self . videoEnabled ;
2015-03-24 13:39:51 -05:00
}
$ . FSRTC . prototype . createAnswer = function ( params ) {
2014-06-21 05:26:06 +05:00
var self = this ;
self . type = "answer" ;
2015-03-24 13:39:51 -05:00
self . remoteSDP = params . sdp ;
console . debug ( "inbound sdp: " , params . sdp ) ;
2014-06-21 05:26:06 +05:00
function onSuccess ( stream ) {
self . localStream = stream ;
2016-06-08 16:20:18 -05:00
self . peer = FSRTCPeerConnection ( {
2014-06-21 05:26:06 +05:00
type : self . type ,
attachStream : self . localStream ,
onICE : function ( candidate ) {
return onICE ( self , candidate ) ;
} ,
onICEComplete : function ( ) {
return onICEComplete ( self ) ;
} ,
onRemoteStream : function ( stream ) {
return onRemoteStream ( self , stream ) ;
} ,
onICESDP : function ( sdp ) {
return onICESDP ( self , sdp ) ;
} ,
onChannelError : function ( e ) {
return onChannelError ( self , e ) ;
} ,
constraints : self . constraints ,
2014-11-11 23:33:03 +08:00
iceServers : self . options . iceServers ,
2014-06-21 05:26:06 +05:00
offerSDP : {
type : "offer" ,
sdp : self . remoteSDP
2018-06-28 17:40:34 -03:00
} ,
turnServer : self . options . turnServer
2014-06-21 05:26:06 +05:00
} ) ;
2016-10-13 10:11:57 -07:00
onStreamSuccess ( self , stream ) ;
2014-06-21 05:26:06 +05:00
}
2014-12-06 11:40:44 -06:00
function onError ( e ) {
onStreamError ( self , e ) ;
2014-06-21 05:26:06 +05:00
}
2015-03-24 13:39:51 -05:00
var mediaParams = getMediaParams ( self ) ;
console . log ( "Audio constraints" , mediaParams . audio ) ;
console . log ( "Video constraints" , mediaParams . video ) ;
2018-07-26 17:03:24 -05:00
if ( self . options . useVideo && self . options . localVideo && ! self . options . useStream ) {
2015-03-24 13:39:51 -05:00
getUserMedia ( {
constraints : {
audio : false ,
2018-07-18 17:58:16 -03:00
video : { deviceId : params . useCamera } ,
2015-03-24 13:39:51 -05:00
} ,
2018-12-31 17:38:53 -03:00
localVideo : self . options . localVideo ,
useCameraLabel : self . options . useCameraLabel ,
useMicLabel : self . options . useMicLabel ,
2015-03-24 13:39:51 -05:00
onsuccess : function ( e ) { self . options . localVideoStream = e ; console . log ( "local video ready" ) ; } ,
2018-12-31 17:38:53 -03:00
onerror : function ( e ) { console . error ( "local video error!" , e ) ; }
2015-03-24 13:39:51 -05:00
} ) ;
}
2018-07-26 17:03:24 -05:00
if ( self . options . useStream ) {
if ( self . options . useVideo ) {
self . options . localVideoStream = self . options . useStream ;
if ( self . options . localVideo ) {
activateLocalVideo ( self . options . localVideo , self . options . useStream ) ;
}
}
onSuccess ( self . options . useStream ) ;
}
else {
getUserMedia ( {
constraints : {
audio : mediaParams . audio ,
video : mediaParams . video
} ,
video : mediaParams . useVideo ,
2018-12-31 17:38:53 -03:00
useCameraLabel : self . options . useCameraLabel ,
useMicLabel : self . options . useMicLabel ,
2018-07-26 17:03:24 -05:00
onsuccess : onSuccess ,
onerror : onError
} ) ;
}
2015-03-24 13:39:51 -05:00
} ;
function getMediaParams ( obj ) {
2014-12-06 11:40:44 -06:00
var audio ;
2015-09-25 20:00:03 -05:00
if ( obj . options . useMic && obj . options . useMic === "none" ) {
console . log ( "Microphone Disabled" ) ;
audio = false ;
} else if ( obj . options . videoParams && obj . options . screenShare ) { //obj.options.videoParams.chromeMediaSource == 'desktop') {
2016-06-08 16:20:18 -05:00
console . error ( "SCREEN SHARE" , obj . options . videoParams ) ;
2014-12-06 11:40:44 -06:00
audio = false ;
} else {
audio = {
} ;
2015-03-24 13:39:51 -05:00
2016-10-04 21:56:11 -03:00
if ( obj . options . audioParams ) {
audio = obj . options . audioParams ;
2017-06-01 18:36:38 -05:00
}
2016-10-04 21:56:11 -03:00
2015-03-24 13:39:51 -05:00
if ( obj . options . useMic !== "any" ) {
2017-09-20 18:00:39 -05:00
//audio.optional = [{sourceId: obj.options.useMic}];
2018-12-31 17:38:53 -03:00
audio . deviceId = assignMediaIdToConstraint ( obj . options . useMic ) ;
2015-03-24 13:39:51 -05:00
}
2014-12-06 11:40:44 -06:00
}
2018-07-26 17:03:24 -05:00
if ( obj . options . useVideo && obj . options . localVideo && ! obj . options . useStream ) {
2015-03-24 13:39:51 -05:00
getUserMedia ( {
constraints : {
audio : false ,
2018-07-18 17:58:16 -03:00
video : { deviceId : obj . options . useCamera } ,
2015-03-24 13:39:51 -05:00
} ,
2018-12-31 17:38:53 -03:00
localVideo : obj . options . localVideo ,
useCameraLabel : obj . options . useCameraLabel ,
useMicLabel : obj . options . useMicLabel ,
2018-07-11 15:49:02 -03:00
onsuccess : function ( e ) { obj . options . localVideoStream = e ; console . log ( "local video ready" ) ; } ,
2018-12-31 17:38:53 -03:00
onerror : function ( e ) { console . error ( "local video error!" , e ) ; }
2015-03-24 13:39:51 -05:00
} ) ;
}
2014-11-24 18:38:52 -05:00
2015-06-18 12:47:04 -05:00
var video = { } ;
2015-06-30 14:46:57 -05:00
var bestFrameRate = obj . options . videoParams . vertoBestFrameRate ;
2016-06-08 16:20:18 -05:00
var minFrameRate = obj . options . videoParams . minFrameRate || 15 ;
2015-06-30 14:46:57 -05:00
delete obj . options . videoParams . vertoBestFrameRate ;
2015-03-24 13:39:51 -05:00
2016-06-08 16:20:18 -05:00
if ( obj . options . screenShare ) {
2017-06-02 12:34:40 -05:00
if ( ! obj . options . useCamera && ! ! navigator . mozGetUserMedia ) {
//This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way
var dowin = window . confirm ( "Do you want to share an application window? If not you can share an entire screen." ) ;
2016-09-27 12:04:23 -05:00
2017-06-01 18:36:38 -05:00
video = {
width : { min : obj . options . videoParams . minWidth , max : obj . options . videoParams . maxWidth } ,
height : { min : obj . options . videoParams . minHeight , max : obj . options . videoParams . maxHeight } ,
mediaSource : dowin ? "window" : "screen"
}
} else {
var opt = [ ] ;
2017-06-02 12:34:40 -05:00
if ( obj . options . useCamera ) {
opt . push ( { sourceId : obj . options . useCamera } ) ;
}
2017-06-01 18:36:38 -05:00
if ( bestFrameRate ) {
opt . push ( { minFrameRate : bestFrameRate } ) ;
opt . push ( { maxFrameRate : bestFrameRate } ) ;
}
video = {
mandatory : obj . options . videoParams ,
optional : opt
} ;
2016-09-27 12:04:23 -05:00
}
2016-06-08 16:20:18 -05:00
} else {
2015-03-24 13:39:51 -05:00
2016-06-08 16:20:18 -05:00
video = {
//mandatory: obj.options.videoParams,
width : { min : obj . options . videoParams . minWidth , max : obj . options . videoParams . maxWidth } ,
height : { min : obj . options . videoParams . minHeight , max : obj . options . videoParams . maxHeight }
} ;
var useVideo = obj . options . useVideo ;
2015-07-21 10:59:17 -05:00
2016-06-08 16:20:18 -05:00
if ( useVideo && obj . options . useCamera && obj . options . useCamera !== "none" ) {
//if (!video.optional) {
//video.optional = [];
//}
2015-06-30 14:46:57 -05:00
2016-06-08 16:20:18 -05:00
if ( obj . options . useCamera !== "any" ) {
//video.optional.push({sourceId: obj.options.useCamera});
2018-12-31 17:38:53 -03:00
video = assignMediaIdToConstraint ( obj . options . useCamera , video ) ;
2016-06-08 16:20:18 -05:00
}
2015-06-30 14:46:57 -05:00
2016-06-08 16:20:18 -05:00
if ( bestFrameRate ) {
//video.optional.push({minFrameRate: bestFrameRate});
//video.optional.push({maxFrameRate: bestFrameRate});
video . frameRate = { ideal : bestFrameRate , min : minFrameRate , max : 30 } ;
}
} else {
console . log ( "Camera Disabled" ) ;
video = false ;
useVideo = false ;
}
2015-03-24 13:39:51 -05:00
}
return { audio : audio , video : video , useVideo : useVideo } ;
}
2014-06-21 05:26:06 +05:00
$ . FSRTC . prototype . call = function ( profile ) {
checkCompat ( ) ;
2015-03-24 13:39:51 -05:00
2014-06-21 05:26:06 +05:00
var self = this ;
2015-03-24 13:39:51 -05:00
var screen = false ;
2014-06-21 05:26:06 +05:00
self . type = "offer" ;
2015-03-24 13:39:51 -05:00
if ( self . options . videoParams && self . options . screenShare ) { //self.options.videoParams.chromeMediaSource == 'desktop') {
screen = true ;
}
2014-06-21 05:26:06 +05:00
function onSuccess ( stream ) {
2015-03-24 13:39:51 -05:00
self . localStream = stream ;
2015-08-26 12:04:07 -05:00
if ( screen ) {
2016-06-08 16:20:18 -05:00
self . constraints . offerToReceiveVideo = false ;
2017-06-02 12:34:40 -05:00
self . constraints . offerToReceiveAudio = false ;
self . constraints . offerToSendAudio = false ;
2015-08-26 12:04:07 -05:00
}
2015-09-03 16:59:55 -05:00
2016-06-08 16:20:18 -05:00
self . peer = FSRTCPeerConnection ( {
2014-06-21 05:26:06 +05:00
type : self . type ,
attachStream : self . localStream ,
onICE : function ( candidate ) {
return onICE ( self , candidate ) ;
} ,
onICEComplete : function ( ) {
return onICEComplete ( self ) ;
} ,
2015-08-26 12:04:07 -05:00
onRemoteStream : screen ? function ( stream ) { } : function ( stream ) {
2014-06-21 05:26:06 +05:00
return onRemoteStream ( self , stream ) ;
} ,
onOfferSDP : function ( sdp ) {
return onOfferSDP ( self , sdp ) ;
} ,
onICESDP : function ( sdp ) {
return onICESDP ( self , sdp ) ;
} ,
onChannelError : function ( e ) {
return onChannelError ( self , e ) ;
} ,
2014-07-31 22:45:47 +05:00
constraints : self . constraints ,
2014-11-11 23:33:03 +08:00
iceServers : self . options . iceServers ,
2018-06-28 17:40:34 -03:00
turnServer : self . options . turnServer
2017-06-01 18:40:02 -05:00
} ) ;
2014-06-21 05:26:06 +05:00
2015-03-24 13:39:51 -05:00
onStreamSuccess ( self , stream ) ;
2014-06-21 05:26:06 +05:00
}
2014-12-06 11:40:44 -06:00
function onError ( e ) {
onStreamError ( self , e ) ;
2014-06-21 05:26:06 +05:00
}
2015-03-24 13:39:51 -05:00
var mediaParams = getMediaParams ( self ) ;
2014-12-06 11:40:44 -06:00
2015-03-24 13:39:51 -05:00
console . log ( "Audio constraints" , mediaParams . audio ) ;
console . log ( "Video constraints" , mediaParams . video ) ;
2015-09-17 13:29:47 -05:00
2018-07-26 17:03:24 -05:00
if ( self . options . useStream ) {
if ( self . options . useVideo ) {
self . options . localVideoStream = self . options . useStream ;
if ( self . options . localVideo ) {
activateLocalVideo ( self . options . localVideo , self . options . useStream ) ;
}
}
onSuccess ( self . options . useStream ) ;
}
else if ( mediaParams . audio || mediaParams . video ) {
2015-09-25 20:00:03 -05:00
getUserMedia ( {
constraints : {
audio : mediaParams . audio ,
2017-06-01 18:36:38 -05:00
video : mediaParams . video
2015-09-25 20:00:03 -05:00
} ,
video : mediaParams . useVideo ,
onsuccess : onSuccess ,
2018-12-31 17:38:53 -03:00
onerror : onError ,
useCameraLabel : self . options . useCameraLabel ,
useMicLabel : self . options . useMicLabel ,
2015-09-25 20:00:03 -05:00
} ) ;
2017-06-02 12:34:40 -05:00
2015-09-25 20:00:03 -05:00
} else {
onSuccess ( null ) ;
}
2015-03-24 13:39:51 -05:00
2014-06-21 05:26:06 +05:00
/ *
2014-11-11 23:33:03 +08:00
navigator . getUserMedia ( {
2015-03-24 13:39:51 -05:00
video : self . options . useVideo ,
2014-06-21 05:26:06 +05:00
audio : true
2014-11-11 23:33:03 +08:00
} , onSuccess , onError ) ;
* /
2014-06-21 05:26:06 +05:00
} ;
// DERIVED from RTCPeerConnection-v1.5
// 2013, @muazkh - github.com/muaz-khan
// MIT License - https://www.webrtc-experiment.com/licence/
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCPeerConnection
2016-06-08 16:20:18 -05:00
function FSRTCPeerConnection ( options ) {
2015-09-03 16:59:55 -05:00
var gathering = false , done = false ;
2016-06-08 16:20:18 -05:00
var config = { } ;
2018-06-28 17:40:34 -03:00
var default _ice = [ { urls : [ 'stun:stun.l.google.com:19302' ] } ] ;
if ( self . options . turnServer ) {
default _ice . push ( self . options . turnServer )
}
2014-07-31 22:45:47 +05:00
2014-11-11 23:33:03 +08:00
if ( options . iceServers ) {
2016-06-08 16:20:18 -05:00
if ( typeof ( options . iceServers ) === "boolean" ) {
2018-06-28 17:40:34 -03:00
config . iceServers = default _ice ;
2016-06-08 16:20:18 -05:00
} else {
config . iceServers = options . iceServers ;
}
2014-06-21 05:26:06 +05:00
}
2017-12-29 16:04:44 -06:00
config . bundlePolicy = "max-compat" ;
2018-10-21 14:50:04 -07:00
config . sdpSemantics = "plan-b" ;
2017-12-29 16:04:44 -06:00
2016-06-08 16:20:18 -05:00
var peer = new window . RTCPeerConnection ( config ) ;
2014-06-21 05:26:06 +05:00
openOffererChannel ( ) ;
var x = 0 ;
2015-09-03 16:59:55 -05:00
function ice _handler ( ) {
done = true ;
gathering = null ;
if ( options . onICEComplete ) {
options . onICEComplete ( ) ;
}
if ( options . type == "offer" ) {
2016-06-08 16:20:18 -05:00
options . onICESDP ( peer . localDescription ) ;
2014-06-21 05:26:06 +05:00
} else {
2015-09-03 16:59:55 -05:00
if ( ! x && options . onICESDP ) {
options . onICESDP ( peer . localDescription ) ;
}
2014-06-21 05:26:06 +05:00
}
2015-09-03 16:59:55 -05:00
}
peer . onicecandidate = function ( event ) {
if ( done ) {
return ;
}
if ( ! gathering ) {
gathering = setTimeout ( ice _handler , 1000 ) ;
}
if ( event ) {
if ( event . candidate ) {
options . onICE ( event . candidate ) ;
}
} else {
done = true ;
if ( gathering ) {
clearTimeout ( gathering ) ;
gathering = null ;
}
ice _handler ( ) ;
}
2014-06-21 05:26:06 +05:00
} ;
2015-09-03 16:59:55 -05:00
2014-06-21 05:26:06 +05:00
// attachStream = MediaStream;
if ( options . attachStream ) peer . addStream ( options . attachStream ) ;
// attachStreams[0] = audio-stream;
// attachStreams[1] = video-stream;
// attachStreams[2] = screen-capturing-stream;
if ( options . attachStreams && options . attachStream . length ) {
var streams = options . attachStreams ;
for ( var i = 0 ; i < streams . length ; i ++ ) {
peer . addStream ( streams [ i ] ) ;
}
}
peer . onaddstream = function ( event ) {
var remoteMediaStream = event . stream ;
// onRemoteStreamEnded(MediaStream)
2016-10-11 15:37:34 -07:00
remoteMediaStream . oninactive = function ( ) {
2014-06-21 05:26:06 +05:00
if ( options . onRemoteStreamEnded ) options . onRemoteStreamEnded ( remoteMediaStream ) ;
} ;
// onRemoteStream(MediaStream)
if ( options . onRemoteStream ) options . onRemoteStream ( remoteMediaStream ) ;
//console.debug('on:add:stream', remoteMediaStream);
} ;
2016-06-08 16:20:18 -05:00
//var constraints = options.constraints || {
// offerToReceiveAudio: true,
//offerToReceiveVideo: true
//};
2014-06-21 05:26:06 +05:00
// onOfferSDP(RTCSessionDescription)
function createOffer ( ) {
if ( ! options . onOfferSDP ) return ;
peer . createOffer ( function ( sessionDescription ) {
sessionDescription . sdp = serializeSdp ( sessionDescription . sdp ) ;
peer . setLocalDescription ( sessionDescription ) ;
options . onOfferSDP ( sessionDescription ) ;
} ,
2016-06-08 16:20:18 -05:00
onSdpError , options . constraints ) ;
2014-06-21 05:26:06 +05:00
}
// onAnswerSDP(RTCSessionDescription)
function createAnswer ( ) {
if ( options . type != "answer" ) return ;
//options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
2016-06-08 16:20:18 -05:00
peer . setRemoteDescription ( new window . RTCSessionDescription ( options . offerSDP ) , onSdpSuccess , onSdpError ) ;
2014-06-21 05:26:06 +05:00
peer . createAnswer ( function ( sessionDescription ) {
sessionDescription . sdp = serializeSdp ( sessionDescription . sdp ) ;
peer . setLocalDescription ( sessionDescription ) ;
if ( options . onAnswerSDP ) {
options . onAnswerSDP ( sessionDescription ) ;
}
} ,
2016-06-08 16:20:18 -05:00
onSdpError ) ;
2014-06-21 05:26:06 +05:00
}
2016-06-08 16:20:18 -05:00
if ( ( options . onChannelMessage ) || ! options . onChannelMessage ) {
2014-06-21 05:26:06 +05:00
createOffer ( ) ;
createAnswer ( ) ;
}
// DataChannel Bandwidth
function setBandwidth ( sdp ) {
// remove existing bandwidth lines
sdp = sdp . replace ( /b=AS([^\r\n]+\r\n)/g , '' ) ;
sdp = sdp . replace ( /a=mid:data\r\n/g , 'a=mid:data\r\nb=AS:1638400\r\n' ) ;
return sdp ;
}
// old: FF<>Chrome interoperability management
function getInteropSDP ( sdp ) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . split ( '' ) ,
extractedChars = '' ;
function getChars ( ) {
extractedChars += chars [ parseInt ( Math . random ( ) * 40 ) ] || '' ;
if ( extractedChars . length < 40 ) getChars ( ) ;
return extractedChars ;
}
// usually audio-only streaming failure occurs out of audio-specific crypto line
// a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
if ( options . onAnswerSDP ) sdp = sdp . replace ( /(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g , '' ) ;
// video-specific crypto line i.e. SHA1_80
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
var inline = getChars ( ) + '\r\n' + ( extractedChars = '' ) ;
sdp = sdp . indexOf ( 'a=crypto' ) == - 1 ? sdp . replace ( /c=IN/g , 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:' + inline + 'c=IN' ) : sdp ;
return sdp ;
}
function serializeSdp ( sdp ) {
return sdp ;
}
// DataChannel management
var channel ;
function openOffererChannel ( ) {
2016-06-08 16:20:18 -05:00
if ( ! options . onChannelMessage ) return ;
2014-06-21 05:26:06 +05:00
_openOffererChannel ( ) ;
2016-06-08 16:20:18 -05:00
return ;
2014-06-21 05:26:06 +05:00
}
function _openOffererChannel ( ) {
2016-06-08 16:20:18 -05:00
channel = peer . createDataChannel ( options . channel || 'RTCDataChannel' , {
2014-06-21 05:26:06 +05:00
reliable : false
} ) ;
setChannelEvents ( ) ;
}
function setChannelEvents ( ) {
channel . onmessage = function ( event ) {
if ( options . onChannelMessage ) options . onChannelMessage ( event ) ;
} ;
channel . onopen = function ( ) {
if ( options . onChannelOpened ) options . onChannelOpened ( channel ) ;
} ;
channel . onclose = function ( event ) {
if ( options . onChannelClosed ) options . onChannelClosed ( event ) ;
console . warn ( 'WebRTC DataChannel closed' , event ) ;
} ;
channel . onerror = function ( event ) {
if ( options . onChannelError ) options . onChannelError ( event ) ;
console . error ( 'WebRTC DataChannel error' , event ) ;
} ;
}
function openAnswererChannel ( ) {
peer . ondatachannel = function ( event ) {
channel = event . channel ;
channel . binaryType = 'blob' ;
setChannelEvents ( ) ;
} ;
2016-06-08 16:20:18 -05:00
return ;
2014-06-21 05:26:06 +05:00
}
// fake:true is also available on chrome under a flag!
function useless ( ) {
log ( 'Error in fake:true' ) ;
}
function onSdpSuccess ( ) { }
function onSdpError ( e ) {
if ( options . onChannelError ) {
options . onChannelError ( e ) ;
}
console . error ( 'sdp error:' , e ) ;
}
return {
addAnswerSDP : function ( sdp , cbSuccess , cbError ) {
2016-06-08 16:20:18 -05:00
peer . setRemoteDescription ( new window . RTCSessionDescription ( sdp ) , cbSuccess ? cbSuccess : onSdpSuccess , cbError ? cbError : onSdpError ) ;
2014-06-21 05:26:06 +05:00
} ,
addICE : function ( candidate ) {
2016-06-08 16:20:18 -05:00
peer . addIceCandidate ( new window . RTCIceCandidate ( {
2014-06-21 05:26:06 +05:00
sdpMLineIndex : candidate . sdpMLineIndex ,
candidate : candidate . candidate
} ) ) ;
} ,
peer : peer ,
channel : channel ,
sendData : function ( message ) {
if ( channel ) {
2014-11-11 23:33:03 +08:00
channel . send ( message ) ;
}
2014-06-21 05:26:06 +05:00
} ,
stop : function ( ) {
peer . close ( ) ;
if ( options . attachStream ) {
2015-09-25 10:30:35 -03:00
if ( typeof options . attachStream . stop == 'function' ) {
2014-06-21 05:26:06 +05:00
options . attachStream . stop ( ) ;
2015-09-25 10:30:35 -03:00
} else {
options . attachStream . active = false ;
}
2014-06-21 05:26:06 +05:00
}
}
} ;
}
// getUserMedia
var video _constraints = {
2016-06-08 16:20:18 -05:00
//mandatory: {},
//optional: []
2014-06-21 05:26:06 +05:00
} ;
2018-07-26 17:03:24 -05:00
function activateLocalVideo ( el , stream ) {
el . srcObject = stream ;
el . style . display = 'block' ;
}
function deactivateLocalVideo ( el ) {
el . srcObject = null ;
el . style . display = 'none' ;
}
2018-12-31 17:38:53 -03:00
function assureConstraintByLabel ( constraint , fallbackLabel ) {
if ( fallbackLabel === undefined && constraint === undefined ) {
return Promise . resolve ( constraint ) ;
}
if ( typeof ( assureMediaInputId ) !== 'function' ) {
console . warn ( 'Tried to use constraint fallbacks but did not found vendor function `assureMediaInputId` on window scope. Did you forget to import `vendor/media-device-id.js` before Verto?' ) ;
return Promise . resolve ( constraint ) ;
}
if ( typeof ( constraint ) === 'object' && ! constraint . deviceId ) {
return Promise . resolve ( constraint ) ;
}
if ( constraint . deviceId ) {
if ( typeof ( constraint . deviceId ) === 'string' ) {
return new Promise ( function ( resolve ) {
assureMediaInputId ( fallbackLabel , constraint . deviceId ) . then ( function ( id ) {
resolve ( Object . assign ( { } , constraint , { deviceId : id } ) ) ;
} ) . catch ( function ( ) {
resolve ( constraint ) ;
} ) ;
} ) ;
}
if ( typeof ( constraint . deviceId ) === 'object' && typeof ( constraint . deviceId . exact ) === 'string' ) {
return new Promise ( function ( resolve ) {
assureMediaInputId ( fallbackLabel , constraint . deviceId . exact ) . then ( function ( id ) {
resolve ( assignMediaIdToConstraint ( id , constraint ) ) ;
} ) . catch ( function ( ) {
resolve ( constraint ) ;
} ) ;
} ) ;
}
}
return Promise . resolve ( constraint ) ;
}
2014-06-21 05:26:06 +05:00
2018-12-31 17:38:53 -03:00
function trustyGetUserMedia ( options , constraints ) {
navigator . mediaDevices . getUserMedia ( constraints ) . then ( function ( stream ) {
2015-03-24 13:39:51 -05:00
if ( options . localVideo ) {
2018-12-31 17:38:53 -03:00
activateLocalVideo ( options . localVideo , stream ) ;
2014-06-21 05:26:06 +05:00
}
2015-03-24 13:39:51 -05:00
2014-06-21 05:26:06 +05:00
if ( options . onsuccess ) {
2014-11-11 23:33:03 +08:00
options . onsuccess ( stream ) ;
}
2018-12-31 17:38:53 -03:00
} ) . catch ( options . onerror || function ( e ) {
console . error ( e ) ;
} ) ;
}
2015-03-24 13:39:51 -05:00
2018-12-31 17:38:53 -03:00
function assignMediaIdToConstraint ( mediaId , rest ) {
return Object . assign ( { } , rest || { } , { deviceId : { exact : mediaId } } ) ;
}
2014-06-21 05:26:06 +05:00
2018-12-31 17:38:53 -03:00
function getUserMedia ( options ) {
var constraints = options . constraints || {
audio : true ,
video : video _constraints ,
} ;
Promise . all ( [
assureConstraintByLabel ( constraints . audio , options . useMicLabel ) ,
assureConstraintByLabel ( constraints . video , options . useCameraLabel ) ,
] ) . then ( function ( assurances ) {
trustyGetUserMedia ( options , { audio : assurances [ 0 ] , video : assurances [ 1 ] } ) ;
} ) . catch ( function ( error ) {
console . error ( 'Unexpected error on media id assurance attempts:' , error , 'Options:' , options ) ;
} ) ;
2014-06-21 05:26:06 +05:00
}
2015-04-28 15:56:25 -05:00
$ . FSRTC . resSupported = function ( w , h ) {
for ( var i in $ . FSRTC . validRes ) {
if ( $ . FSRTC . validRes [ i ] [ 0 ] == w && $ . FSRTC . validRes [ i ] [ 1 ] == h ) {
return true ;
}
}
return false ;
}
$ . FSRTC . bestResSupported = function ( ) {
var w = 0 , h = 0 ;
for ( var i in $ . FSRTC . validRes ) {
2016-10-10 15:02:33 -04:00
if ( $ . FSRTC . validRes [ i ] [ 0 ] >= w && $ . FSRTC . validRes [ i ] [ 1 ] >= h ) {
2015-04-28 15:56:25 -05:00
w = $ . FSRTC . validRes [ i ] [ 0 ] ;
h = $ . FSRTC . validRes [ i ] [ 1 ] ;
}
}
return [ w , h ] ;
}
2016-06-18 15:02:35 -05:00
var resList = [ [ 160 , 120 ] , [ 320 , 180 ] , [ 320 , 240 ] , [ 640 , 360 ] , [ 640 , 480 ] , [ 1280 , 720 ] , [ 1920 , 1080 ] ] ;
2015-04-30 13:40:39 -05:00
var resI = 0 ;
2015-06-18 13:00:42 -05:00
var ttl = 0 ;
2015-04-30 13:40:39 -05:00
var checkRes = function ( cam , func ) {
if ( resI >= resList . length ) {
2015-08-18 15:28:32 -03:00
var res = {
'validRes' : $ . FSRTC . validRes ,
'bestResSupported' : $ . FSRTC . bestResSupported ( )
} ;
2015-08-28 09:42:37 -05:00
localStorage . setItem ( "res_" + cam , $ . toJSON ( res ) ) ;
2015-08-18 15:28:32 -03:00
if ( func ) return func ( res ) ;
2015-04-30 13:40:39 -05:00
return ;
}
w = resList [ resI ] [ 0 ] ;
h = resList [ resI ] [ 1 ] ;
resI ++ ;
2018-07-26 14:36:02 -05:00
var video = {
2017-03-16 10:37:31 -05:00
width : { exact : w } ,
height : { exact : h }
2015-04-28 15:56:25 -05:00
} ;
2018-07-26 14:36:02 -05:00
if ( cam !== "any" ) {
2018-12-31 17:38:53 -03:00
video = assignMediaIdToConstraint ( cam , video ) ;
2018-07-26 14:36:02 -05:00
}
2015-04-28 15:56:25 -05:00
getUserMedia ( {
constraints : {
2015-06-18 13:00:42 -05:00
audio : ttl ++ == 0 ,
2015-04-28 15:56:25 -05:00
video : video
} ,
2015-09-25 10:30:35 -03:00
onsuccess : function ( e ) {
2015-09-30 01:56:50 -05:00
e . getTracks ( ) . forEach ( function ( track ) { track . stop ( ) ; } ) ;
console . info ( w + "x" + h + " supported." ) ; $ . FSRTC . validRes . push ( [ w , h ] ) ; checkRes ( cam , func ) ; } ,
2016-10-11 15:51:32 -07:00
onerror : function ( e ) { console . warn ( w + "x" + h + " not supported." ) ; checkRes ( cam , func ) ; }
2015-04-28 15:56:25 -05:00
} ) ;
}
2015-04-30 13:40:39 -05:00
$ . FSRTC . getValidRes = function ( cam , func ) {
var used = [ ] ;
2015-08-28 09:42:37 -05:00
var cached = localStorage . getItem ( "res_" + cam ) ;
if ( cached ) {
2015-08-28 14:28:48 -05:00
var cache = $ . parseJSON ( cached ) ;
2015-08-28 14:32:48 -05:00
if ( cache ) {
$ . FSRTC . validRes = cache . validRes ;
console . log ( "CACHED RES FOR CAM " + cam , cache ) ;
} else {
console . error ( "INVALID CACHE" ) ;
}
2015-08-28 14:28:48 -05:00
return func ? func ( cache ) : null ;
2015-08-28 09:42:37 -05:00
}
2015-04-28 15:56:25 -05:00
$ . FSRTC . validRes = [ ] ;
2015-04-30 13:40:39 -05:00
resI = 0 ;
2015-04-28 15:56:25 -05:00
2015-04-30 13:40:39 -05:00
checkRes ( cam , func ) ;
2015-04-28 15:56:25 -05:00
}
2015-09-17 15:09:19 -05:00
$ . FSRTC . checkPerms = function ( runtime , check _audio , check _video ) {
2015-04-28 15:56:25 -05:00
getUserMedia ( {
constraints : {
2015-09-17 15:09:19 -05:00
audio : check _audio ,
video : check _video ,
2015-04-28 15:56:25 -05:00
} ,
2015-09-25 10:30:35 -03:00
onsuccess : function ( e ) {
2017-05-02 17:12:19 -05:00
2015-09-30 01:56:50 -05:00
e . getTracks ( ) . forEach ( function ( track ) { track . stop ( ) ; } ) ;
console . info ( "media perm init complete" ) ;
if ( runtime ) {
setTimeout ( runtime , 100 , true ) ;
}
2015-09-25 10:30:35 -03:00
} ,
2015-09-17 15:09:19 -05:00
onerror : function ( e ) {
if ( check _video && check _audio ) {
console . error ( "error, retesting with audio params only" ) ;
return $ . FSRTC . checkPerms ( runtime , check _audio , false ) ;
}
console . error ( "media perm init error" ) ;
if ( runtime ) {
runtime ( false )
}
}
2015-04-28 15:56:25 -05:00
} ) ;
}
2014-06-21 05:26:06 +05:00
} ) ( jQuery ) ;