2014-08-21 09:58:33 +00:00
/*jslint plusplus: true */
/*jslint nomen: true*/
/ * *
* Created by gp on 11 / 08 / 14.
* /
function Simulcast ( ) {
"use strict" ;
// TODO(gp) split the Simulcast class in two classes : NativeSimulcast and ClassicSimulcast.
this . debugLvl = 1 ;
}
( function ( ) {
"use strict" ;
// global state for all transformers.
var localExplosionMap = { } , localVideoSourceCache , emptyCompoundIndex ,
2014-09-12 17:54:40 +00:00
remoteVideoSourceCache , remoteMaps = {
2014-08-21 09:58:33 +00:00
msid2Quality : { } ,
ssrc2Msid : { } ,
receivingVideoStreams : { }
} , localMaps = {
msids : [ ] ,
msid2ssrc : { }
} ;
Simulcast . prototype . _generateGuid = ( function ( ) {
function s4 ( ) {
return Math . floor ( ( 1 + Math . random ( ) ) * 0x10000 )
. toString ( 16 )
. substring ( 1 ) ;
}
return function ( ) {
return s4 ( ) + s4 ( ) + '-' + s4 ( ) + '-' + s4 ( ) + '-' +
s4 ( ) + '-' + s4 ( ) + s4 ( ) + s4 ( ) ;
} ;
} ( ) ) ;
Simulcast . prototype . _cacheVideoSources = function ( lines ) {
localVideoSourceCache = this . _getVideoSources ( lines ) ;
} ;
Simulcast . prototype . _restoreVideoSources = function ( lines ) {
this . _replaceVideoSources ( lines , localVideoSourceCache ) ;
} ;
2014-09-12 17:54:40 +00:00
Simulcast . prototype . _cacheRemoteVideoSources = function ( lines ) {
remoteVideoSourceCache = this . _getVideoSources ( lines ) ;
} ;
Simulcast . prototype . _restoreRemoteVideoSources = function ( lines ) {
this . _replaceVideoSources ( lines , remoteVideoSourceCache ) ;
} ;
2014-08-21 09:58:33 +00:00
Simulcast . prototype . _replaceVideoSources = function ( lines , videoSources ) {
var i , inVideo = false , index = - 1 , howMany = 0 ;
if ( this . debugLvl ) {
console . info ( 'Replacing video sources...' ) ;
}
for ( i = 0 ; i < lines . length ; i ++ ) {
if ( inVideo && lines [ i ] . substring ( 0 , 'm=' . length ) === 'm=' ) {
// Out of video.
break ;
}
if ( ! inVideo && lines [ i ] . substring ( 0 , 'm=video ' . length ) === 'm=video ' ) {
// In video.
inVideo = true ;
}
if ( inVideo && ( lines [ i ] . substring ( 0 , 'a=ssrc:' . length ) === 'a=ssrc:'
|| lines [ i ] . substring ( 0 , 'a=ssrc-group:' . length ) === 'a=ssrc-group:' ) ) {
if ( index === - 1 ) {
index = i ;
}
howMany ++ ;
}
}
// efficiency baby ;)
lines . splice . apply ( lines ,
[ index , howMany ] . concat ( videoSources ) ) ;
} ;
Simulcast . prototype . _getVideoSources = function ( lines ) {
var i , inVideo = false , sb = [ ] ;
if ( this . debugLvl ) {
console . info ( 'Getting video sources...' ) ;
}
for ( i = 0 ; i < lines . length ; i ++ ) {
if ( inVideo && lines [ i ] . substring ( 0 , 'm=' . length ) === 'm=' ) {
// Out of video.
break ;
}
if ( ! inVideo && lines [ i ] . substring ( 0 , 'm=video ' . length ) === 'm=video ' ) {
// In video.
inVideo = true ;
}
if ( inVideo && lines [ i ] . substring ( 0 , 'a=ssrc:' . length ) === 'a=ssrc:' ) {
// In SSRC.
sb . push ( lines [ i ] ) ;
}
if ( inVideo && lines [ i ] . substring ( 0 , 'a=ssrc-group:' . length ) === 'a=ssrc-group:' ) {
sb . push ( lines [ i ] ) ;
}
}
return sb ;
} ;
Simulcast . prototype . _parseMedia = function ( lines , mediatypes ) {
var i , res = [ ] , type , cur _media , idx , ssrcs , cur _ssrc , ssrc ,
ssrc _attribute , group , semantics , skip ;
if ( this . debugLvl ) {
console . info ( 'Parsing media sources...' ) ;
}
for ( i = 0 ; i < lines . length ; i ++ ) {
if ( lines [ i ] . substring ( 0 , 'm=' . length ) === 'm=' ) {
type = lines [ i ]
. substr ( 'm=' . length , lines [ i ] . indexOf ( ' ' ) - 'm=' . length ) ;
skip = mediatypes !== undefined && mediatypes . indexOf ( type ) === - 1 ;
if ( ! skip ) {
cur _media = {
'type' : type ,
'sources' : { } ,
'groups' : [ ]
} ;
res . push ( cur _media ) ;
}
} else if ( ! skip && lines [ i ] . substring ( 0 , 'a=ssrc:' . length ) === 'a=ssrc:' ) {
idx = lines [ i ] . indexOf ( ' ' ) ;
ssrc = lines [ i ] . substring ( 'a=ssrc:' . length , idx ) ;
if ( cur _media . sources [ ssrc ] === undefined ) {
cur _ssrc = { 'ssrc' : ssrc } ;
cur _media . sources [ ssrc ] = cur _ssrc ;
}
ssrc _attribute = lines [ i ] . substr ( idx + 1 ) . split ( ':' , 2 ) [ 0 ] ;
cur _ssrc [ ssrc _attribute ] = lines [ i ] . substr ( idx + 1 ) . split ( ':' , 2 ) [ 1 ] ;
if ( cur _media . base === undefined ) {
cur _media . base = cur _ssrc ;
}
} else if ( ! skip && lines [ i ] . substring ( 0 , 'a=ssrc-group:' . length ) === 'a=ssrc-group:' ) {
idx = lines [ i ] . indexOf ( ' ' ) ;
semantics = lines [ i ] . substr ( 0 , idx ) . substr ( 'a=ssrc-group:' . length ) ;
ssrcs = lines [ i ] . substr ( idx ) . trim ( ) . split ( ' ' ) ;
group = {
'semantics' : semantics ,
'ssrcs' : ssrcs
} ;
cur _media . groups . push ( group ) ;
} else if ( ! skip && ( lines [ i ] . substring ( 0 , 'a=sendrecv' . length ) === 'a=sendrecv' ||
lines [ i ] . substring ( 0 , 'a=recvonly' . length ) === 'a=recvonly' ||
lines [ i ] . substring ( 0 , 'a=sendonly' . length ) === 'a=sendonly' ||
lines [ i ] . substring ( 0 , 'a=inactive' . length ) === 'a=inactive' ) ) {
cur _media . direction = lines [ i ] . substring ( 'a=' . length , 8 ) ;
}
}
return res ;
} ;
// Returns a random integer between min (included) and max (excluded)
// Using Math.round() will give you a non-uniform distribution!
Simulcast . prototype . _generateRandomSSRC = function ( ) {
var min = 0 , max = 0xffffffff ;
return Math . floor ( Math . random ( ) * ( max - min ) ) + min ;
} ;
function CompoundIndex ( obj ) {
if ( obj !== undefined ) {
this . row = obj . row ;
this . column = obj . column ;
}
}
emptyCompoundIndex = new CompoundIndex ( ) ;
Simulcast . prototype . _indexOfArray = function ( needle , haystack , start ) {
var length = haystack . length , idx , i ;
if ( ! start ) {
start = 0 ;
}
for ( i = start ; i < length ; i ++ ) {
idx = haystack [ i ] . indexOf ( needle ) ;
if ( idx !== - 1 ) {
return new CompoundIndex ( { row : i , column : idx } ) ;
}
}
return emptyCompoundIndex ;
} ;
Simulcast . prototype . _removeSimulcastGroup = function ( lines ) {
var i ;
for ( i = lines . length - 1 ; i >= 0 ; i -- ) {
if ( lines [ i ] . indexOf ( 'a=ssrc-group:SIM' ) !== - 1 ) {
lines . splice ( i , 1 ) ;
}
}
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Produces a single stream with multiple tracks for local video sources .
*
* @ param lines
* @ private
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . _explodeLocalSimulcastSources = function ( lines ) {
var sb , msid , sid , tid , videoSources , self ;
if ( this . debugLvl ) {
console . info ( 'Exploding local video sources...' ) ;
}
videoSources = this . _parseMedia ( lines , [ 'video' ] ) [ 0 ] ;
self = this ;
if ( videoSources . groups && videoSources . groups . length !== 0 ) {
videoSources . groups . forEach ( function ( group ) {
if ( group . semantics === 'SIM' ) {
group . ssrcs . forEach ( function ( ssrc ) {
// Get the msid for this ssrc..
if ( localExplosionMap [ ssrc ] ) {
// .. either from the explosion map..
msid = localExplosionMap [ ssrc ] ;
} else {
// .. or generate a new one (msid).
sid = videoSources . sources [ ssrc ] . msid
. substring ( 0 , videoSources . sources [ ssrc ] . msid . indexOf ( ' ' ) ) ;
tid = self . _generateGuid ( ) ;
msid = [ sid , tid ] . join ( ' ' ) ;
localExplosionMap [ ssrc ] = msid ;
}
// Assign it to the source object.
videoSources . sources [ ssrc ] . msid = msid ;
// TODO(gp) Change the msid of associated sources.
} ) ;
}
} ) ;
}
sb = this . _compileVideoSources ( videoSources ) ;
this . _replaceVideoSources ( lines , sb ) ;
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Groups local video sources together in the ssrc - group : SIM group .
*
* @ param lines
* @ private
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . _groupLocalVideoSources = function ( lines ) {
var sb , videoSources , ssrcs = [ ] , ssrc ;
if ( this . debugLvl ) {
console . info ( 'Grouping local video sources...' ) ;
}
videoSources = this . _parseMedia ( lines , [ 'video' ] ) [ 0 ] ;
for ( ssrc in videoSources . sources ) {
// jitsi-meet destroys/creates streams at various places causing
// the original local stream ids to change. The only thing that
// remains unchanged is the trackid.
localMaps . msid2ssrc [ videoSources . sources [ ssrc ] . msid . split ( ' ' ) [ 1 ] ] = ssrc ;
}
// TODO(gp) add only "free" sources.
localMaps . msids . forEach ( function ( msid ) {
ssrcs . push ( localMaps . msid2ssrc [ msid ] ) ;
} ) ;
if ( ! videoSources . groups ) {
videoSources . groups = [ ] ;
}
videoSources . groups . push ( {
'semantics' : 'SIM' ,
'ssrcs' : ssrcs
} ) ;
sb = this . _compileVideoSources ( videoSources ) ;
this . _replaceVideoSources ( lines , sb ) ;
} ;
Simulcast . prototype . _appendSimulcastGroup = function ( lines ) {
var videoSources , ssrcGroup , simSSRC , numOfSubs = 3 , i , sb , msid ;
if ( this . debugLvl ) {
console . info ( 'Appending simulcast group...' ) ;
}
// Get the primary SSRC information.
videoSources = this . _parseMedia ( lines , [ 'video' ] ) [ 0 ] ;
// Start building the SIM SSRC group.
ssrcGroup = [ 'a=ssrc-group:SIM' ] ;
// The video source buffer.
sb = [ ] ;
// Create the simulcast sub-streams.
for ( i = 0 ; i < numOfSubs ; i ++ ) {
// TODO(gp) prevent SSRC collision.
simSSRC = this . _generateRandomSSRC ( ) ;
ssrcGroup . push ( simSSRC ) ;
sb . splice . apply ( sb , [ sb . length , 0 ] . concat (
[ [ "a=ssrc:" , simSSRC , " cname:" , videoSources . base . cname ] . join ( '' ) ,
[ "a=ssrc:" , simSSRC , " msid:" , videoSources . base . msid ] . join ( '' ) ]
) ) ;
if ( this . debugLvl ) {
console . info ( [ 'Generated substream ' , i , ' with SSRC ' , simSSRC , '.' ] . join ( '' ) ) ;
}
}
// Add the group sim layers.
sb . splice ( 0 , 0 , ssrcGroup . join ( ' ' ) )
this . _replaceVideoSources ( lines , sb ) ;
} ;
// Does the actual patching.
Simulcast . prototype . _ensureSimulcastGroup = function ( lines ) {
if ( this . debugLvl ) {
console . info ( 'Ensuring simulcast group...' ) ;
}
if ( this . _indexOfArray ( 'a=ssrc-group:SIM' , lines ) === emptyCompoundIndex ) {
this . _appendSimulcastGroup ( lines ) ;
this . _cacheVideoSources ( lines ) ;
} else {
// verify that the ssrcs participating in the SIM group are present
// in the SDP (needed for presence).
this . _restoreVideoSources ( lines ) ;
}
} ;
Simulcast . prototype . _ensureGoogConference = function ( lines ) {
var sb ;
if ( this . debugLvl ) {
console . info ( 'Ensuring x-google-conference flag...' )
}
if ( this . _indexOfArray ( 'a=x-google-flag:conference' , lines ) === emptyCompoundIndex ) {
// Add the google conference flag
sb = this . _getVideoSources ( lines ) ;
sb = [ 'a=x-google-flag:conference' ] . concat ( sb ) ;
this . _replaceVideoSources ( lines , sb ) ;
}
} ;
Simulcast . prototype . _compileVideoSources = function ( videoSources ) {
var sb = [ ] , ssrc , addedSSRCs = [ ] ;
if ( this . debugLvl ) {
console . info ( 'Compiling video sources...' ) ;
}
// Add the groups
if ( videoSources . groups && videoSources . groups . length !== 0 ) {
videoSources . groups . forEach ( function ( group ) {
if ( group . ssrcs && group . ssrcs . length !== 0 ) {
sb . push ( [ [ 'a=ssrc-group:' , group . semantics ] . join ( '' ) , group . ssrcs . join ( ' ' ) ] . join ( ' ' ) ) ;
// if (group.semantics !== 'SIM') {
group . ssrcs . forEach ( function ( ssrc ) {
addedSSRCs . push ( ssrc ) ;
sb . splice . apply ( sb , [ sb . length , 0 ] . concat ( [
[ "a=ssrc:" , ssrc , " cname:" , videoSources . sources [ ssrc ] . cname ] . join ( '' ) ,
[ "a=ssrc:" , ssrc , " msid:" , videoSources . sources [ ssrc ] . msid ] . join ( '' ) ] ) ) ;
} ) ;
//}
}
} ) ;
}
// Then add any free sources.
if ( videoSources . sources ) {
for ( ssrc in videoSources . sources ) {
if ( addedSSRCs . indexOf ( ssrc ) === - 1 ) {
sb . splice . apply ( sb , [ sb . length , 0 ] . concat ( [
[ "a=ssrc:" , ssrc , " cname:" , videoSources . sources [ ssrc ] . cname ] . join ( '' ) ,
[ "a=ssrc:" , ssrc , " msid:" , videoSources . sources [ ssrc ] . msid ] . join ( '' ) ] ) ) ;
}
}
}
return sb ;
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Ensures that the simulcast group is present in the answer , _if _ native
* simulcast is enabled ,
*
* @ param desc
* @ returns { * }
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . transformAnswer = function ( desc ) {
if ( config . enableSimulcast && config . useNativeSimulcast ) {
var sb = desc . sdp . split ( '\r\n' ) ;
// Even if we have enabled native simulcasting previously
// (with a call to SLD with an appropriate SDP, for example),
// createAnswer seems to consistently generate incomplete SDP
// with missing SSRCS.
//
// So, subsequent calls to SLD will have missing SSRCS and presence
// won't have the complete list of SRCs.
this . _ensureSimulcastGroup ( sb ) ;
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
if ( this . debugLvl && this . debugLvl > 1 ) {
console . info ( 'Transformed answer' ) ;
console . info ( desc . sdp ) ;
}
}
return desc ;
} ;
2014-09-12 17:54:40 +00:00
Simulcast . prototype . _restoreSimulcastGroups = function ( sb ) {
this . _restoreRemoteVideoSources ( sb ) ;
} ;
/ * *
* Restores the simulcast groups of the remote description . In
* transformRemoteDescription we remove those in order for the set remote
* description to succeed . The focus needs the signal the groups to new
* participants .
*
* @ param desc
* @ returns { * }
* /
Simulcast . prototype . reverseTransformRemoteDescription = function ( desc ) {
2014-08-21 09:58:33 +00:00
var sb ;
2014-09-12 17:54:40 +00:00
if ( ! desc || desc == null ) {
2014-08-21 09:58:33 +00:00
return desc ;
2014-09-12 17:54:40 +00:00
}
if ( config . enableSimulcast ) {
sb = desc . sdp . split ( '\r\n' ) ;
this . _restoreSimulcastGroups ( sb ) ;
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
}
return desc ;
} ;
/ * *
* Prepares the local description for public usage ( i . e . to be signaled
* through Jingle to the focus ) .
*
* @ param desc
* @ returns { RTCSessionDescription }
* /
Simulcast . prototype . reverseTransformLocalDescription = function ( desc ) {
var sb ;
if ( ! desc || desc == null ) {
return desc ;
}
2014-08-21 09:58:33 +00:00
if ( config . enableSimulcast ) {
if ( config . useNativeSimulcast ) {
sb = desc . sdp . split ( '\r\n' ) ;
this . _explodeLocalSimulcastSources ( sb ) ;
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
if ( this . debugLvl && this . debugLvl > 1 ) {
console . info ( 'Exploded local video sources' ) ;
console . info ( desc . sdp ) ;
}
} else {
sb = desc . sdp . split ( '\r\n' ) ;
this . _groupLocalVideoSources ( sb ) ;
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
if ( this . debugLvl && this . debugLvl > 1 ) {
console . info ( 'Grouped local video sources' ) ;
console . info ( desc . sdp ) ;
}
}
}
return desc ;
} ;
Simulcast . prototype . _ensureOrder = function ( lines ) {
var videoSources , sb ;
videoSources = this . _parseMedia ( lines , [ 'video' ] ) [ 0 ] ;
sb = this . _compileVideoSources ( videoSources ) ;
this . _replaceVideoSources ( lines , sb ) ;
} ;
Simulcast . prototype . _updateRemoteMaps = function ( lines ) {
var remoteVideoSources = this . _parseMedia ( lines , [ 'video' ] ) [ 0 ] , videoSource , quality ;
2014-09-12 17:54:40 +00:00
// (re) initialize the remote maps.
remoteMaps = {
msid2Quality : { } ,
ssrc2Msid : { } ,
receivingVideoStreams : { }
} ;
2014-08-21 09:58:33 +00:00
if ( remoteVideoSources . groups && remoteVideoSources . groups . length !== 0 ) {
remoteVideoSources . groups . forEach ( function ( group ) {
if ( group . semantics === 'SIM' && group . ssrcs && group . ssrcs . length !== 0 ) {
quality = 0 ;
group . ssrcs . forEach ( function ( ssrc ) {
videoSource = remoteVideoSources . sources [ ssrc ] ;
remoteMaps . msid2Quality [ videoSource . msid ] = quality ++ ;
remoteMaps . ssrc2Msid [ videoSource . ssrc ] = videoSource . msid ;
} ) ;
}
} ) ;
}
} ;
2014-09-12 17:54:40 +00:00
/ * *
*
*
* @ param desc
* @ returns { * }
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . transformLocalDescription = function ( desc ) {
if ( config . enableSimulcast && ! config . useNativeSimulcast ) {
var sb = desc . sdp . split ( '\r\n' ) ;
this . _removeSimulcastGroup ( sb ) ;
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
if ( this . debugLvl && this . debugLvl > 1 ) {
console . info ( 'Transformed local description' ) ;
console . info ( desc . sdp ) ;
}
}
return desc ;
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Removes the ssrc - group : SIM from the remote description bacause Chrome
* either gets confused and thinks this is an FID group or , if an FID group
* is already present , it fails to set the remote description .
*
* @ param desc
* @ returns { * }
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . transformRemoteDescription = function ( desc ) {
if ( config . enableSimulcast ) {
var sb = desc . sdp . split ( '\r\n' ) ;
this . _updateRemoteMaps ( sb ) ;
2014-09-12 17:54:40 +00:00
this . _cacheRemoteVideoSources ( sb ) ;
this . _removeSimulcastGroup ( sb ) ; // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
if ( config . useNativeSimulcast ) {
// We don't need the goog conference flag if we're not doing
// native simulcast.
this . _ensureGoogConference ( sb ) ;
}
2014-08-21 09:58:33 +00:00
desc = new RTCSessionDescription ( {
type : desc . type ,
sdp : sb . join ( '\r\n' )
} ) ;
if ( this . debugLvl && this . debugLvl > 1 ) {
console . info ( 'Transformed remote description' ) ;
console . info ( desc . sdp ) ;
}
}
return desc ;
} ;
2014-09-12 17:54:40 +00:00
Simulcast . prototype . _setReceivingVideoStream = function ( ssrc ) {
2014-08-21 09:58:33 +00:00
var receivingTrack = remoteMaps . ssrc2Msid [ ssrc ] ,
msidParts = receivingTrack . split ( ' ' ) ;
remoteMaps . receivingVideoStreams [ msidParts [ 0 ] ] = msidParts [ 1 ] ;
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Returns a stream with single video track , the one currently being
* received by this endpoint .
*
* @ param stream the remote simulcast stream .
* @ returns { webkitMediaStream }
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . getReceivingVideoStream = function ( stream ) {
2014-09-12 17:54:40 +00:00
var tracks , i , electedTrack , msid , quality = 1 , receivingTrackId ;
2014-08-21 09:58:33 +00:00
if ( config . enableSimulcast ) {
if ( remoteMaps . receivingVideoStreams [ stream . id ] )
{
2014-09-12 17:54:40 +00:00
// the bridge has signaled us to receive a specific track.
2014-08-21 09:58:33 +00:00
receivingTrackId = remoteMaps . receivingVideoStreams [ stream . id ] ;
tracks = stream . getVideoTracks ( ) ;
for ( i = 0 ; i < tracks . length ; i ++ ) {
if ( receivingTrackId === tracks [ i ] . id ) {
electedTrack = tracks [ i ] ;
break ;
}
}
}
if ( ! electedTrack ) {
2014-09-12 17:54:40 +00:00
// we don't have an elected track, choose by initial quality.
2014-08-21 09:58:33 +00:00
tracks = stream . getVideoTracks ( ) ;
for ( i = 0 ; i < tracks . length ; i ++ ) {
2014-09-12 17:54:40 +00:00
msid = [ stream . id , tracks [ i ] . id ] . join ( ' ' ) ;
2014-08-21 09:58:33 +00:00
if ( remoteMaps . msid2Quality [ msid ] === quality ) {
2014-09-12 17:54:40 +00:00
electedTrack = tracks [ i ] ;
2014-08-21 09:58:33 +00:00
break ;
}
}
2014-09-12 17:54:40 +00:00
// TODO(gp) if the initialQuality could not be satisfied, lower
// the requirement and try again.
2014-08-21 09:58:33 +00:00
}
}
return ( electedTrack )
? new webkitMediaStream ( [ electedTrack ] )
: stream ;
} ;
2014-09-12 17:54:40 +00:00
var stream ;
/ * *
* GUM for simulcast .
*
* @ param constraints
* @ param success
* @ param err
* /
2014-08-21 09:58:33 +00:00
Simulcast . prototype . getUserMedia = function ( constraints , success , err ) {
// TODO(gp) what if we request a resolution not supported by the hardware?
// TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
var lqConstraints = {
audio : false ,
video : {
mandatory : {
maxWidth : 320 ,
maxHeight : 180
}
}
} ;
if ( config . enableSimulcast && ! config . useNativeSimulcast ) {
2014-09-12 17:54:40 +00:00
// NOTE(gp) if we request the lq stream first webkitGetUserMedia
// fails randomly. Tested with Chrome 37. As fippo suggested, the
// reason appears to be that Chrome only acquires the cam once and
// then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
2014-08-21 09:58:33 +00:00
navigator . webkitGetUserMedia ( constraints , function ( hqStream ) {
// reset local maps.
localMaps . msids = [ ] ;
localMaps . msid2ssrc = { } ;
// add hq trackid to local map
localMaps . msids . push ( hqStream . getVideoTracks ( ) [ 0 ] . id ) ;
navigator . webkitGetUserMedia ( lqConstraints , function ( lqStream ) {
2014-09-12 11:37:57 +00:00
// NOTE(gp) The specification says Array.forEach() will visit
// the array elements in numeric order, and that it doesn't
// visit elements that don't exist.
2014-08-21 09:58:33 +00:00
// add lq trackid to local map
2014-09-12 11:37:57 +00:00
localMaps . msids . splice ( 0 , 0 , lqStream . getVideoTracks ( ) [ 0 ] . id ) ;
2014-08-21 09:58:33 +00:00
hqStream . addTrack ( lqStream . getVideoTracks ( ) [ 0 ] ) ;
2014-09-12 17:54:40 +00:00
stream = hqStream ;
2014-08-21 09:58:33 +00:00
success ( hqStream ) ;
} , err ) ;
} , err ) ;
} else {
// There's nothing special to do for native simulcast, so just do a normal GUM.
navigator . webkitGetUserMedia ( constraints , function ( hqStream ) {
// reset local maps.
localMaps . msids = [ ] ;
localMaps . msid2ssrc = { } ;
// add hq stream to local map
localMaps . msids . push ( hqStream . getVideoTracks ( ) [ 0 ] . id ) ;
2014-09-12 17:54:40 +00:00
stream = hqStream ;
2014-08-21 09:58:33 +00:00
success ( hqStream ) ;
} , err ) ;
}
} ;
2014-09-12 17:54:40 +00:00
/ * *
* Gets the fully qualified msid ( stream . id + track . id ) associated to the
* SSRC .
*
* @ param ssrc
* @ returns { * }
* /
Simulcast . prototype . getRemoteVideoStreamIdBySSRC = function ( ssrc ) {
return remoteMaps . ssrc2Msid [ ssrc ] ;
2014-08-21 09:58:33 +00:00
} ;
Simulcast . prototype . parseMedia = function ( desc , mediatypes ) {
var lines = desc . sdp . split ( '\r\n' ) ;
return this . _parseMedia ( lines , mediatypes ) ;
} ;
2014-09-12 17:54:40 +00:00
Simulcast . prototype . _startLocalVideoStream = function ( ssrc ) {
var trackid ;
Object . keys ( localMaps . msid2ssrc ) . some ( function ( tid ) {
if ( localMaps . msid2ssrc [ tid ] == ssrc )
{
trackid = tid ;
return true ;
}
} ) ;
stream . getVideoTracks ( ) . some ( function ( track ) {
if ( track . id === trackid ) {
track . enabled = true ;
return true ;
}
} ) ;
} ;
Simulcast . prototype . _stopLocalVideoStream = function ( ssrc ) {
var trackid ;
Object . keys ( localMaps . msid2ssrc ) . some ( function ( tid ) {
if ( localMaps . msid2ssrc [ tid ] == ssrc )
{
trackid = tid ;
return true ;
}
} ) ;
stream . getVideoTracks ( ) . some ( function ( track ) {
if ( track . id === trackid ) {
track . enabled = false ;
return true ;
}
} ) ;
} ;
Simulcast . prototype . getLocalVideoStream = function ( ) {
var track ;
stream . getVideoTracks ( ) . some ( function ( t ) {
if ( ( track = t ) . enabled ) {
return true ;
}
} ) ;
return new webkitMediaStream ( [ track ] ) ;
} ;
$ ( document ) . bind ( 'simulcastlayerschanged' , function ( event , endpointSimulcastLayers ) {
endpointSimulcastLayers . forEach ( function ( esl ) {
var ssrc = esl . simulcastLayer . primarySSRC ;
var simulcast = new Simulcast ( ) ;
simulcast . _setReceivingVideoStream ( ssrc ) ;
} ) ;
} ) ;
$ ( document ) . bind ( 'startsimulcastlayer' , function ( event , simulcastLayer ) {
var ssrc = simulcastLayer . primarySSRC ;
var simulcast = new Simulcast ( ) ;
simulcast . _startLocalVideoStream ( ssrc ) ;
2014-09-16 13:43:41 +00:00
$ ( document ) . trigger ( 'simulcastlayerstarted' ) ;
2014-09-12 17:54:40 +00:00
} ) ;
$ ( document ) . bind ( 'stopsimulcastlayer' , function ( event , simulcastLayer ) {
var ssrc = simulcastLayer . primarySSRC ;
var simulcast = new Simulcast ( ) ;
simulcast . _stopLocalVideoStream ( ssrc ) ;
2014-09-16 13:43:41 +00:00
$ ( document ) . trigger ( 'simulcastlayerstopped' ) ;
2014-09-12 17:54:40 +00:00
} ) ;
2014-08-21 09:58:33 +00:00
} ( ) ) ;