2018-01-16 04:15:58 +08:00
( function ( f ) { if ( typeof exports === "object" && typeof module !== "undefined" ) { module . exports = f ( ) } else if ( typeof define === "function" && define . amd ) { define ( [ ] , f ) } else { var g ; if ( typeof window !== "undefined" ) { g = window } else if ( typeof global !== "undefined" ) { g = global } else if ( typeof self !== "undefined" ) { g = self } else { g = this } g . kurentoUtils = f ( ) } } ) ( function ( ) { var define , module , exports ; return ( function e ( t , n , r ) { function s ( o , u ) { if ( ! n [ o ] ) { if ( ! t [ o ] ) { var a = typeof require == "function" && require ; if ( ! u && a ) return a ( o , ! 0 ) ; if ( i ) return i ( o , ! 0 ) ; var f = new Error ( "Cannot find module '" + o + "'" ) ; throw f . code = "MODULE_NOT_FOUND" , f } var l = n [ o ] = { exports : { } } ; t [ o ] [ 0 ] . call ( l . exports , function ( e ) { var n = t [ o ] [ 1 ] [ e ] ; return s ( n ? n : e ) } , l , l . exports , e , t , n , r ) } return n [ o ] . exports } var i = typeof require == "function" && require ; for ( var o = 0 ; o < r . length ; o ++ ) s ( r [ o ] ) ; return s } ) ( { 1 : [ function ( require , module , exports ) {
var freeice = require ( 'freeice' ) ;
var inherits = require ( 'inherits' ) ;
var UAParser = require ( 'ua-parser-js' ) ;
var uuid = require ( 'uuid' ) ;
var hark = require ( 'hark' ) ;
var EventEmitter = require ( 'events' ) . EventEmitter ;
var recursive = require ( 'merge' ) . recursive . bind ( undefined , true ) ;
var sdpTranslator = require ( 'sdp-translator' ) ;
var logger = window . Logger || console ;
try {
require ( 'kurento-browser-extensions' ) ;
} catch ( error ) {
if ( typeof getScreenConstraints === 'undefined' ) {
logger . warn ( 'screen sharing is not available' ) ;
getScreenConstraints = function getScreenConstraints ( sendSource , callback ) {
callback ( new Error ( 'This library is not enabled for screen sharing' ) ) ;
} ;
}
}
var MEDIA _CONSTRAINTS = {
audio : true ,
video : {
width : 640 ,
framerate : 15
}
} ;
var ua = window && window . navigator ? window . navigator . userAgent : '' ;
var parser = new UAParser ( ua ) ;
var browser = parser . getBrowser ( ) ;
var usePlanB = false ;
if ( browser . name === 'Chrome' || browser . name === 'Chromium' ) {
logger . debug ( browser . name + ': using SDP PlanB' ) ;
usePlanB = true ;
}
function noop ( error ) {
if ( error )
logger . error ( error ) ;
}
function trackStop ( track ) {
2021-07-06 00:01:13 +08:00
if ( track && typeof track . stop === 'function' && track . readyState !== 'ended' ) {
track . stop ( ) ;
// Manually emit the event as a safeguard; Firefox doesn't fire it when it
// should with live MediaStreamTracks...
const trackStoppedEvt = new MediaStreamTrackEvent ( 'ended' , { track } ) ;
track . dispatchEvent ( trackStoppedEvt ) ;
}
2018-01-16 04:15:58 +08:00
}
function streamStop ( stream ) {
2018-10-04 02:39:55 +08:00
let track = stream . track ;
trackStop ( track ) ;
2018-01-16 04:15:58 +08:00
}
2018-10-04 02:39:55 +08:00
2018-01-16 04:15:58 +08:00
var dumpSDP = function ( description ) {
if ( typeof description === 'undefined' || description === null ) {
return '' ;
}
return 'type: ' + description . type + '\r\n' + description . sdp ;
} ;
function bufferizeCandidates ( pc , onerror ) {
var candidatesQueue = [ ] ;
pc . addEventListener ( 'signalingstatechange' , function ( ) {
if ( this . signalingState === 'stable' ) {
while ( candidatesQueue . length ) {
var entry = candidatesQueue . shift ( ) ;
this . addIceCandidate ( entry . candidate , entry . callback , entry . callback ) ;
}
}
} ) ;
return function ( candidate , callback ) {
callback = callback || onerror ;
switch ( pc . signalingState ) {
case 'closed' :
callback ( new Error ( 'PeerConnection object is closed' ) ) ;
break ;
case 'stable' :
if ( pc . remoteDescription ) {
2018-10-04 02:39:55 +08:00
pc . addIceCandidate ( candidate ) . then ( callback ) . catch ( err => {
callback ( err ) ;
} ) ;
2018-01-16 04:15:58 +08:00
break ;
}
default :
candidatesQueue . push ( {
candidate : candidate ,
callback : callback
} ) ;
}
} ;
}
function removeFIDFromOffer ( sdp ) {
var n = sdp . indexOf ( 'a=ssrc-group:FID' ) ;
if ( n > 0 ) {
return sdp . slice ( 0 , n ) ;
} else {
return sdp ;
}
}
function getSimulcastInfo ( videoStream ) {
var videoTracks = videoStream . getVideoTracks ( ) ;
if ( ! videoTracks . length ) {
logger . warn ( 'No video tracks available in the video stream' ) ;
return '' ;
}
var lines = [
'a=x-google-flag:conference' ,
'a=ssrc-group:SIM 1 2 3' ,
'a=ssrc:1 cname:localVideo' ,
'a=ssrc:1 msid:' + videoStream . id + ' ' + videoTracks [ 0 ] . id ,
'a=ssrc:1 mslabel:' + videoStream . id ,
'a=ssrc:1 label:' + videoTracks [ 0 ] . id ,
'a=ssrc:2 cname:localVideo' ,
'a=ssrc:2 msid:' + videoStream . id + ' ' + videoTracks [ 0 ] . id ,
'a=ssrc:2 mslabel:' + videoStream . id ,
'a=ssrc:2 label:' + videoTracks [ 0 ] . id ,
'a=ssrc:3 cname:localVideo' ,
'a=ssrc:3 msid:' + videoStream . id + ' ' + videoTracks [ 0 ] . id ,
'a=ssrc:3 mslabel:' + videoStream . id ,
'a=ssrc:3 label:' + videoTracks [ 0 ] . id
] ;
lines . push ( '' ) ;
return lines . join ( '\n' ) ;
}
function WebRtcPeer ( mode , options , callback ) {
if ( ! ( this instanceof WebRtcPeer ) ) {
return new WebRtcPeer ( mode , options , callback ) ;
}
WebRtcPeer . super _ . call ( this ) ;
if ( options instanceof Function ) {
callback = options ;
options = undefined ;
}
options = options || { } ;
callback = ( callback || noop ) . bind ( this ) ;
var self = this ;
2019-07-05 00:53:40 +08:00
const userAgent = window . navigator . userAgent . toLocaleLowerCase ( ) ;
2018-01-16 04:15:58 +08:00
var localVideo = options . localVideo ;
var remoteVideo = options . remoteVideo ;
var videoStream = options . videoStream ;
var audioStream = options . audioStream ;
var mediaConstraints = options . mediaConstraints ;
var connectionConstraints = options . connectionConstraints ;
var pc = options . peerConnection ;
var sendSource = options . sendSource || 'webcam' ;
var dataChannelConfig = options . dataChannelConfig ;
var useDataChannels = options . dataChannels || false ;
var dataChannel ;
var guid = uuid . v4 ( ) ;
var configuration = recursive ( { iceServers : freeice ( ) } , options . configuration ) ;
var onicecandidate = options . onicecandidate ;
if ( onicecandidate )
this . on ( 'icecandidate' , onicecandidate ) ;
var oncandidategatheringdone = options . oncandidategatheringdone ;
if ( oncandidategatheringdone ) {
this . on ( 'candidategatheringdone' , oncandidategatheringdone ) ;
}
var simulcast = options . simulcast ;
var multistream = options . multistream ;
var interop = new sdpTranslator . Interop ( ) ;
var candidatesQueueOut = [ ] ;
var candidategatheringdone = false ;
Object . defineProperties ( this , {
'peerConnection' : {
get : function ( ) {
return pc ;
}
} ,
'id' : {
value : options . id || guid ,
writable : false
} ,
'remoteVideo' : {
get : function ( ) {
return remoteVideo ;
}
} ,
'localVideo' : {
get : function ( ) {
return localVideo ;
}
} ,
'dataChannel' : {
get : function ( ) {
return dataChannel ;
}
} ,
'currentFrame' : {
get : function ( ) {
if ( ! remoteVideo )
return ;
if ( remoteVideo . readyState < remoteVideo . HAVE _CURRENT _DATA )
throw new Error ( 'No video stream data available' ) ;
var canvas = document . createElement ( 'canvas' ) ;
canvas . width = remoteVideo . videoWidth ;
canvas . height = remoteVideo . videoHeight ;
canvas . getContext ( '2d' ) . drawImage ( remoteVideo , 0 , 0 ) ;
return canvas ;
}
}
} ) ;
if ( ! pc ) {
pc = new RTCPeerConnection ( configuration ) ;
2019-01-11 07:25:39 +08:00
//Add Transceiver for Webview on IOS
if ( ( userAgent . indexOf ( 'iphone' ) > - 1 || userAgent . indexOf ( 'ipad' ) > - 1 ) && userAgent . indexOf ( 'safari' ) == - 1 ) {
try {
pc . addTransceiver ( 'video' ) ;
} catch ( e ) { }
}
2019-01-22 04:25:00 +08:00
2018-01-16 04:15:58 +08:00
if ( useDataChannels && ! dataChannel ) {
var dcId = 'WebRtcPeer-' + self . id ;
var dcOptions = undefined ;
if ( dataChannelConfig ) {
dcId = dataChannelConfig . id || dcId ;
dcOptions = dataChannelConfig . options ;
}
dataChannel = pc . createDataChannel ( dcId , dcOptions ) ;
if ( dataChannelConfig ) {
dataChannel . onopen = dataChannelConfig . onopen ;
dataChannel . onclose = dataChannelConfig . onclose ;
dataChannel . onmessage = dataChannelConfig . onmessage ;
dataChannel . onbufferedamountlow = dataChannelConfig . onbufferedamountlow ;
dataChannel . onerror = dataChannelConfig . onerror || noop ;
}
}
}
pc . addEventListener ( 'icecandidate' , function ( event ) {
var candidate = event . candidate ;
if ( EventEmitter . listenerCount ( self , 'icecandidate' ) || EventEmitter . listenerCount ( self , 'candidategatheringdone' ) ) {
if ( candidate ) {
var cand ;
if ( multistream && usePlanB ) {
cand = interop . candidateToUnifiedPlan ( candidate ) ;
} else {
cand = candidate ;
}
self . emit ( 'icecandidate' , cand ) ;
candidategatheringdone = false ;
} else if ( ! candidategatheringdone ) {
self . emit ( 'candidategatheringdone' ) ;
candidategatheringdone = true ;
}
} else if ( ! candidategatheringdone ) {
candidatesQueueOut . push ( candidate ) ;
if ( ! candidate )
candidategatheringdone = true ;
}
} ) ;
2018-10-04 02:39:55 +08:00
pc . onaddtrack = options . onaddstream ;
2018-01-16 04:15:58 +08:00
pc . onnegotiationneeded = options . onnegotiationneeded ;
this . on ( 'newListener' , function ( event , listener ) {
if ( event === 'icecandidate' || event === 'candidategatheringdone' ) {
while ( candidatesQueueOut . length ) {
var candidate = candidatesQueueOut . shift ( ) ;
if ( ! candidate === ( event === 'candidategatheringdone' ) ) {
listener ( candidate ) ;
}
}
}
} ) ;
var addIceCandidate = bufferizeCandidates ( pc ) ;
this . addIceCandidate = function ( iceCandidate , callback ) {
var candidate ;
if ( multistream && usePlanB ) {
candidate = interop . candidateToPlanB ( iceCandidate ) ;
} else {
candidate = new RTCIceCandidate ( iceCandidate ) ;
}
2018-06-20 01:36:12 +08:00
// logger.debug('Remote ICE candidate received', iceCandidate);
2018-01-16 04:15:58 +08:00
callback = ( callback || noop ) . bind ( this ) ;
addIceCandidate ( candidate , callback ) ;
} ;
this . generateOffer = function ( callback ) {
callback = callback . bind ( this ) ;
2019-01-23 21:23:43 +08:00
const descriptionCallback = ( ) => {
var localDescription = pc . localDescription ;
// logger.debug('Local description set', localDescription.sdp);
if ( multistream && usePlanB ) {
localDescription = interop . toUnifiedPlan ( localDescription ) ;
logger . debug ( 'offer::origPlanB->UnifiedPlan' , dumpSDP ( localDescription ) ) ;
}
callback ( null , localDescription . sdp , self . processAnswer . bind ( self ) ) ;
}
2019-01-11 07:25:39 +08:00
2018-01-16 04:15:58 +08:00
var offerAudio = true ;
var offerVideo = true ;
if ( mediaConstraints ) {
offerAudio = typeof mediaConstraints . audio === 'boolean' ? mediaConstraints . audio : true ;
offerVideo = typeof mediaConstraints . video === 'boolean' ? mediaConstraints . video : true ;
}
var browserDependantConstraints = {
offerToReceiveAudio : mode !== 'sendonly' && offerAudio ,
offerToReceiveVideo : mode !== 'sendonly' && offerVideo
} ;
var constraints = browserDependantConstraints ;
2018-06-20 01:36:12 +08:00
// logger.debug('constraints: ' + JSON.stringify(constraints));
2018-01-16 04:15:58 +08:00
pc . createOffer ( constraints ) . then ( function ( offer ) {
2018-06-20 01:36:12 +08:00
// logger.debug('Created SDP offer');
2018-01-16 04:15:58 +08:00
offer = mangleSdpToAddSimulcast ( offer ) ;
return pc . setLocalDescription ( offer ) ;
2019-01-23 21:23:43 +08:00
} ) . then ( ( ) => {
descriptionCallback ( ) ;
2019-01-22 04:25:00 +08:00
} ) . catch ( callback ) ;
2018-01-16 04:15:58 +08:00
} ;
this . getLocalSessionDescriptor = function ( ) {
return pc . localDescription ;
} ;
this . getRemoteSessionDescriptor = function ( ) {
return pc . remoteDescription ;
} ;
function setRemoteVideo ( ) {
2022-05-07 21:34:05 +08:00
if ( remoteVideo ) {
const stream = self . getRemoteStream ( ) ;
remoteVideo . oncanplaythrough = function ( ) {
remoteVideo . play ( ) . then ( ( ) => {
remoteVideo . muted = false ;
} ) . catch ( ( ) => {
const tagFailedEvent = new CustomEvent ( 'mediaTagPlayFailed' , { detail : { mediaTag : remoteVideo } } ) ;
window . dispatchEvent ( tagFailedEvent ) ;
2019-07-26 01:36:19 +08:00
} ) ;
2022-05-07 21:34:05 +08:00
} ;
2018-08-30 03:12:34 +08:00
2022-05-07 21:34:05 +08:00
remoteVideo . pause ( ) ;
remoteVideo . srcObject = stream ;
remoteVideo . load ( ) ;
}
2018-01-16 04:15:58 +08:00
}
this . showLocalVideo = function ( ) {
localVideo . srcObject = videoStream ;
localVideo . muted = true ;
} ;
this . send = function ( data ) {
if ( dataChannel && dataChannel . readyState === 'open' ) {
dataChannel . send ( data ) ;
} else {
logger . warn ( 'Trying to send data over a non-existing or closed data channel' ) ;
}
} ;
this . processAnswer = function ( sdpAnswer , callback ) {
callback = ( callback || noop ) . bind ( this ) ;
var answer = new RTCSessionDescription ( {
type : 'answer' ,
sdp : sdpAnswer
} ) ;
if ( multistream && usePlanB ) {
var planBAnswer = interop . toPlanB ( answer ) ;
logger . debug ( 'asnwer::planB' , dumpSDP ( planBAnswer ) ) ;
answer = planBAnswer ;
}
2018-06-20 01:36:12 +08:00
// logger.debug('SDP answer received, setting remote description');
2018-01-16 04:15:58 +08:00
if ( pc . signalingState === 'closed' ) {
return callback ( 'PeerConnection is closed' ) ;
}
2018-10-04 02:39:55 +08:00
pc . setRemoteDescription ( answer ) . then ( function ( ) {
2018-01-16 04:15:58 +08:00
setRemoteVideo ( ) ;
callback ( ) ;
2018-10-04 02:39:55 +08:00
} ) . catch ( callback ) ;
2018-01-16 04:15:58 +08:00
} ;
this . processOffer = function ( sdpOffer , callback ) {
callback = callback . bind ( this ) ;
var offer = new RTCSessionDescription ( {
type : 'offer' ,
sdp : sdpOffer
} ) ;
if ( multistream && usePlanB ) {
var planBOffer = interop . toPlanB ( offer ) ;
logger . debug ( 'offer::planB' , dumpSDP ( planBOffer ) ) ;
offer = planBOffer ;
}
2018-06-20 01:36:12 +08:00
// logger.debug('SDP offer received, setting remote description');
2018-01-16 04:15:58 +08:00
if ( pc . signalingState === 'closed' ) {
return callback ( 'PeerConnection is closed' ) ;
}
pc . setRemoteDescription ( offer ) . then ( function ( ) {
return setRemoteVideo ( ) ;
} ) . then ( function ( ) {
return pc . createAnswer ( ) ;
} ) . then ( function ( answer ) {
answer = mangleSdpToAddSimulcast ( answer ) ;
2018-06-20 01:36:12 +08:00
// logger.debug('Created SDP answer');
2018-01-16 04:15:58 +08:00
return pc . setLocalDescription ( answer ) ;
} ) . then ( function ( ) {
var localDescription = pc . localDescription ;
if ( multistream && usePlanB ) {
localDescription = interop . toUnifiedPlan ( localDescription ) ;
logger . debug ( 'answer::origPlanB->UnifiedPlan' , dumpSDP ( localDescription ) ) ;
}
2018-06-20 01:36:12 +08:00
// logger.debug('Local description set', localDescription.sdp);
2018-01-16 04:15:58 +08:00
callback ( null , localDescription . sdp ) ;
} ) . catch ( callback ) ;
} ;
function mangleSdpToAddSimulcast ( answer ) {
if ( simulcast ) {
if ( browser . name === 'Chrome' || browser . name === 'Chromium' ) {
logger . debug ( 'Adding multicast info' ) ;
answer = new RTCSessionDescription ( {
'type' : answer . type ,
'sdp' : removeFIDFromOffer ( answer . sdp ) + getSimulcastInfo ( videoStream )
} ) ;
} else {
logger . warn ( 'Simulcast is only available in Chrome browser.' ) ;
}
}
return answer ;
}
function start ( ) {
if ( pc . signalingState === 'closed' ) {
callback ( 'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue' ) ;
}
if ( videoStream && localVideo ) {
self . showLocalVideo ( ) ;
}
if ( videoStream ) {
2020-05-15 00:31:22 +08:00
if ( typeof videoStream . getTracks === 'function'
&& typeof pc . addTrack === 'function' ) {
videoStream . getTracks ( ) . forEach ( track => pc . addTrack ( track , videoStream ) ) ;
} else {
pc . addStream ( videoStream ) ;
}
2018-01-16 04:15:58 +08:00
}
if ( audioStream ) {
2020-05-15 00:31:22 +08:00
if ( typeof audioStream . getTracks === 'function'
&& typeof pc . addTrack === 'function' ) {
audioStream . getTracks ( ) . forEach ( track => pc . addTrack ( track , audioStream ) ) ;
} else {
pc . addStream ( audioStream ) ;
}
2018-01-16 04:15:58 +08:00
}
var browser = parser . getBrowser ( ) ;
if ( mode === 'sendonly' && ( browser . name === 'Chrome' || browser . name === 'Chromium' ) && browser . major === 39 ) {
mode = 'sendrecv' ;
}
callback ( ) ;
}
if ( mode !== 'recvonly' && ! videoStream && ! audioStream ) {
function getMedia ( constraints ) {
if ( constraints === undefined ) {
constraints = MEDIA _CONSTRAINTS ;
}
navigator . mediaDevices . getUserMedia ( constraints ) . then ( function ( stream ) {
videoStream = stream ;
start ( ) ;
} ) . catch ( callback ) ;
}
if ( sendSource === 'webcam' ) {
getMedia ( mediaConstraints ) ;
} else {
getScreenConstraints ( sendSource , function ( error , constraints _ ) {
if ( error )
return callback ( error ) ;
constraints = [ mediaConstraints ] ;
constraints . unshift ( constraints _ ) ;
2019-01-22 02:02:57 +08:00
let gDMCallback = function ( stream ) {
stream . getTracks ( ) [ 0 ] . applyConstraints ( constraints [ 0 ] . optional )
. then ( ( ) => {
2019-01-04 01:08:41 +08:00
videoStream = stream ;
start ( ) ;
2019-01-04 02:17:13 +08:00
} ) . catch ( ( ) => {
2019-01-22 02:02:57 +08:00
videoStream = stream ;
start ( ) ;
2019-01-04 02:17:13 +08:00
} ) ;
2019-01-22 02:02:57 +08:00
}
if ( typeof navigator . getDisplayMedia === 'function' ) {
navigator . getDisplayMedia ( recursive . apply ( undefined , constraints ) )
. then ( gDMCallback )
. catch ( callback ) ;
2019-01-29 02:48:06 +08:00
} else if ( navigator . mediaDevices && typeof navigator . mediaDevices . getDisplayMedia === 'function' ) {
2019-01-22 02:02:57 +08:00
navigator . mediaDevices . getDisplayMedia ( recursive . apply ( undefined , constraints ) )
. then ( gDMCallback )
. catch ( callback ) ;
2019-01-04 01:08:41 +08:00
} else {
getMedia ( recursive . apply ( undefined , constraints ) ) ;
}
2018-01-16 04:15:58 +08:00
} , guid ) ;
}
} else {
setTimeout ( start , 0 ) ;
}
this . on ( '_dispose' , function ( ) {
if ( localVideo ) {
localVideo . pause ( ) ;
localVideo . src = '' ;
localVideo . load ( ) ;
localVideo . muted = false ;
}
if ( remoteVideo ) {
remoteVideo . pause ( ) ;
remoteVideo . src = '' ;
remoteVideo . load ( ) ;
}
self . removeAllListeners ( ) ;
if ( window . cancelChooseDesktopMedia !== undefined ) {
window . cancelChooseDesktopMedia ( guid ) ;
}
} ) ;
}
inherits ( WebRtcPeer , EventEmitter ) ;
function createEnableDescriptor ( type ) {
var method = 'get' + type + 'Tracks' ;
return {
enumerable : true ,
get : function ( ) {
if ( ! this . peerConnection )
return ;
2018-10-04 02:39:55 +08:00
var senders = this . peerConnection . getSenders ( ) ;
if ( ! senders . length )
2018-01-16 04:15:58 +08:00
return ;
2018-10-04 02:39:55 +08:00
senders . forEach ( sender => {
let track = sender . track ;
if ( ! track . enabled && track . kind === type ) {
return false ;
}
} ) ;
2018-01-16 04:15:58 +08:00
return true ;
} ,
set : function ( value ) {
function trackSetEnable ( track ) {
track . enabled = value ;
}
2018-10-04 02:39:55 +08:00
this . peerConnection . getSenders ( ) . forEach ( function ( stream ) {
let track = stream . track ;
if ( track . kind === type ) {
trackSetEnable ( track ) ;
}
2018-01-16 04:15:58 +08:00
} ) ;
}
} ;
}
Object . defineProperties ( WebRtcPeer . prototype , {
'enabled' : {
enumerable : true ,
get : function ( ) {
return this . audioEnabled && this . videoEnabled ;
} ,
set : function ( value ) {
this . audioEnabled = this . videoEnabled = value ;
}
} ,
'audioEnabled' : createEnableDescriptor ( 'Audio' ) ,
'videoEnabled' : createEnableDescriptor ( 'Video' )
} ) ;
2018-10-04 02:39:55 +08:00
WebRtcPeer . prototype . getLocalStream = function ( ) {
if ( this . localStream ) {
return this . localStream ;
}
if ( this . peerConnection ) {
this . localStream = new MediaStream ( ) ;
this . peerConnection . getSenders ( ) . forEach ( ( ls ) => {
let track = ls . track ;
if ( track && ! track . muted ) {
this . localStream . addTrack ( track ) ;
}
} ) ;
return this . localStream ;
}
2018-01-16 04:15:58 +08:00
} ;
2018-10-04 02:39:55 +08:00
WebRtcPeer . prototype . getRemoteStream = function ( ) {
if ( this . remoteStream ) {
return this . remoteStream ;
}
if ( this . peerConnection ) {
this . remoteStream = new MediaStream ( ) ;
this . peerConnection . getReceivers ( ) . forEach ( ( rs ) => {
let track = rs . track ;
2018-10-10 02:03:40 +08:00
if ( track ) {
if ( track . muted ) {
track . onunmute = ( ) => {
this . remoteStream . addTrack ( track ) ;
} ;
return ;
}
2018-10-04 02:39:55 +08:00
this . remoteStream . addTrack ( track ) ;
}
} ) ;
return this . remoteStream ;
}
2018-01-16 04:15:58 +08:00
} ;
WebRtcPeer . prototype . dispose = function ( ) {
2018-06-20 01:36:12 +08:00
// logger.debug('Disposing WebRtcPeer');
2018-01-16 04:15:58 +08:00
var pc = this . peerConnection ;
var dc = this . dataChannel ;
try {
if ( dc ) {
if ( dc . signalingState === 'closed' )
return ;
dc . close ( ) ;
}
if ( pc ) {
if ( pc . signalingState === 'closed' )
return ;
2018-10-04 02:39:55 +08:00
pc . getSenders ( ) . forEach ( streamStop ) ;
if ( this . remoteStream ) {
this . remoteStream = null ;
}
if ( this . localStream ) {
this . localStream = null ;
}
2018-01-16 04:15:58 +08:00
pc . close ( ) ;
}
} catch ( err ) {
logger . warn ( 'Exception disposing webrtc peer ' + err ) ;
}
this . emit ( '_dispose' ) ;
} ;
function WebRtcPeerRecvonly ( options , callback ) {
if ( ! ( this instanceof WebRtcPeerRecvonly ) ) {
return new WebRtcPeerRecvonly ( options , callback ) ;
}
WebRtcPeerRecvonly . super _ . call ( this , 'recvonly' , options , callback ) ;
}
inherits ( WebRtcPeerRecvonly , WebRtcPeer ) ;
function WebRtcPeerSendonly ( options , callback ) {
if ( ! ( this instanceof WebRtcPeerSendonly ) ) {
return new WebRtcPeerSendonly ( options , callback ) ;
}
WebRtcPeerSendonly . super _ . call ( this , 'sendonly' , options , callback ) ;
}
inherits ( WebRtcPeerSendonly , WebRtcPeer ) ;
function WebRtcPeerSendrecv ( options , callback ) {
if ( ! ( this instanceof WebRtcPeerSendrecv ) ) {
return new WebRtcPeerSendrecv ( options , callback ) ;
}
WebRtcPeerSendrecv . super _ . call ( this , 'sendrecv' , options , callback ) ;
}
inherits ( WebRtcPeerSendrecv , WebRtcPeer ) ;
function harkUtils ( stream , options ) {
return hark ( stream , options ) ;
}
exports . bufferizeCandidates = bufferizeCandidates ;
exports . WebRtcPeerRecvonly = WebRtcPeerRecvonly ;
exports . WebRtcPeerSendonly = WebRtcPeerSendonly ;
exports . WebRtcPeerSendrecv = WebRtcPeerSendrecv ;
exports . hark = harkUtils ;
} , { "events" : 4 , "freeice" : 5 , "hark" : 8 , "inherits" : 9 , "kurento-browser-extensions" : 10 , "merge" : 11 , "sdp-translator" : 18 , "ua-parser-js" : 21 , "uuid" : 23 } ] , 2 : [ function ( require , module , exports ) {
if ( window . addEventListener )
module . exports = require ( './index' ) ;
} , { "./index" : 3 } ] , 3 : [ function ( require , module , exports ) {
var WebRtcPeer = require ( './WebRtcPeer' ) ;
exports . WebRtcPeer = WebRtcPeer ;
} , { "./WebRtcPeer" : 1 } ] , 4 : [ function ( require , module , exports ) {
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
function EventEmitter ( ) {
this . _events = this . _events || { } ;
this . _maxListeners = this . _maxListeners || undefined ;
}
module . exports = EventEmitter ;
// Backwards-compat with node 0.10.x
EventEmitter . EventEmitter = EventEmitter ;
EventEmitter . prototype . _events = undefined ;
EventEmitter . prototype . _maxListeners = undefined ;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter . defaultMaxListeners = 10 ;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter . prototype . setMaxListeners = function ( n ) {
if ( ! isNumber ( n ) || n < 0 || isNaN ( n ) )
throw TypeError ( 'n must be a positive number' ) ;
this . _maxListeners = n ;
return this ;
} ;
EventEmitter . prototype . emit = function ( type ) {
var er , handler , len , args , i , listeners ;
if ( ! this . _events )
this . _events = { } ;
// If there is no 'error' event listener then throw.
if ( type === 'error' ) {
if ( ! this . _events . error ||
( isObject ( this . _events . error ) && ! this . _events . error . length ) ) {
er = arguments [ 1 ] ;
if ( er instanceof Error ) {
throw er ; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error ( 'Uncaught, unspecified "error" event. (' + er + ')' ) ;
err . context = er ;
throw err ;
}
}
}
handler = this . _events [ type ] ;
if ( isUndefined ( handler ) )
return false ;
if ( isFunction ( handler ) ) {
switch ( arguments . length ) {
// fast cases
case 1 :
handler . call ( this ) ;
break ;
case 2 :
handler . call ( this , arguments [ 1 ] ) ;
break ;
case 3 :
handler . call ( this , arguments [ 1 ] , arguments [ 2 ] ) ;
break ;
// slower
default :
args = Array . prototype . slice . call ( arguments , 1 ) ;
handler . apply ( this , args ) ;
}
} else if ( isObject ( handler ) ) {
args = Array . prototype . slice . call ( arguments , 1 ) ;
listeners = handler . slice ( ) ;
len = listeners . length ;
for ( i = 0 ; i < len ; i ++ )
listeners [ i ] . apply ( this , args ) ;
}
return true ;
} ;
EventEmitter . prototype . addListener = function ( type , listener ) {
var m ;
if ( ! isFunction ( listener ) )
throw TypeError ( 'listener must be a function' ) ;
if ( ! this . _events )
this . _events = { } ;
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if ( this . _events . newListener )
this . emit ( 'newListener' , type ,
isFunction ( listener . listener ) ?
listener . listener : listener ) ;
if ( ! this . _events [ type ] )
// Optimize the case of one listener. Don't need the extra array object.
this . _events [ type ] = listener ;
else if ( isObject ( this . _events [ type ] ) )
// If we've already got an array, just append.
this . _events [ type ] . push ( listener ) ;
else
// Adding the second element, need to change to array.
this . _events [ type ] = [ this . _events [ type ] , listener ] ;
// Check for listener leak
if ( isObject ( this . _events [ type ] ) && ! this . _events [ type ] . warned ) {
if ( ! isUndefined ( this . _maxListeners ) ) {
m = this . _maxListeners ;
} else {
m = EventEmitter . defaultMaxListeners ;
}
if ( m && m > 0 && this . _events [ type ] . length > m ) {
this . _events [ type ] . warned = true ;
console . error ( '(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.' ,
this . _events [ type ] . length ) ;
if ( typeof console . trace === 'function' ) {
// not supported in IE 10
console . trace ( ) ;
}
}
}
return this ;
} ;
EventEmitter . prototype . on = EventEmitter . prototype . addListener ;
EventEmitter . prototype . once = function ( type , listener ) {
if ( ! isFunction ( listener ) )
throw TypeError ( 'listener must be a function' ) ;
var fired = false ;
function g ( ) {
this . removeListener ( type , g ) ;
if ( ! fired ) {
fired = true ;
listener . apply ( this , arguments ) ;
}
}
g . listener = listener ;
this . on ( type , g ) ;
return this ;
} ;
// emits a 'removeListener' event iff the listener was removed
EventEmitter . prototype . removeListener = function ( type , listener ) {
var list , position , length , i ;
if ( ! isFunction ( listener ) )
throw TypeError ( 'listener must be a function' ) ;
if ( ! this . _events || ! this . _events [ type ] )
return this ;
list = this . _events [ type ] ;
length = list . length ;
position = - 1 ;
if ( list === listener ||
( isFunction ( list . listener ) && list . listener === listener ) ) {
delete this . _events [ type ] ;
if ( this . _events . removeListener )
this . emit ( 'removeListener' , type , listener ) ;
} else if ( isObject ( list ) ) {
for ( i = length ; i -- > 0 ; ) {
if ( list [ i ] === listener ||
( list [ i ] . listener && list [ i ] . listener === listener ) ) {
position = i ;
break ;
}
}
if ( position < 0 )
return this ;
if ( list . length === 1 ) {
list . length = 0 ;
delete this . _events [ type ] ;
} else {
list . splice ( position , 1 ) ;
}
if ( this . _events . removeListener )
this . emit ( 'removeListener' , type , listener ) ;
}
return this ;
} ;
EventEmitter . prototype . removeAllListeners = function ( type ) {
var key , listeners ;
if ( ! this . _events )
return this ;
// not listening for removeListener, no need to emit
if ( ! this . _events . removeListener ) {
if ( arguments . length === 0 )
this . _events = { } ;
else if ( this . _events [ type ] )
delete this . _events [ type ] ;
return this ;
}
// emit removeListener for all listeners on all events
if ( arguments . length === 0 ) {
for ( key in this . _events ) {
if ( key === 'removeListener' ) continue ;
this . removeAllListeners ( key ) ;
}
this . removeAllListeners ( 'removeListener' ) ;
this . _events = { } ;
return this ;
}
listeners = this . _events [ type ] ;
if ( isFunction ( listeners ) ) {
this . removeListener ( type , listeners ) ;
} else if ( listeners ) {
// LIFO order
while ( listeners . length )
this . removeListener ( type , listeners [ listeners . length - 1 ] ) ;
}
delete this . _events [ type ] ;
return this ;
} ;
EventEmitter . prototype . listeners = function ( type ) {
var ret ;
if ( ! this . _events || ! this . _events [ type ] )
ret = [ ] ;
else if ( isFunction ( this . _events [ type ] ) )
ret = [ this . _events [ type ] ] ;
else
ret = this . _events [ type ] . slice ( ) ;
return ret ;
} ;
EventEmitter . prototype . listenerCount = function ( type ) {
if ( this . _events ) {
var evlistener = this . _events [ type ] ;
if ( isFunction ( evlistener ) )
return 1 ;
else if ( evlistener )
return evlistener . length ;
}
return 0 ;
} ;
EventEmitter . listenerCount = function ( emitter , type ) {
return emitter . listenerCount ( type ) ;
} ;
function isFunction ( arg ) {
return typeof arg === 'function' ;
}
function isNumber ( arg ) {
return typeof arg === 'number' ;
}
function isObject ( arg ) {
return typeof arg === 'object' && arg !== null ;
}
function isUndefined ( arg ) {
return arg === void 0 ;
}
} , { } ] , 5 : [ function ( require , module , exports ) {
/* jshint node: true */
'use strict' ;
var normalice = require ( 'normalice' ) ;
/ * *
# freeice
The ` freeice ` module is a simple way of getting random STUN or TURN server
for your WebRTC application . The list of servers ( just STUN at this stage )
were sourced from this [ gist ] ( https : //gist.github.com/zziuni/3741933).
# # Example Use
The following demonstrates how you can use ` freeice ` with
[ rtc - quickconnect ] ( https : //github.com/rtc-io/rtc-quickconnect):
<< < examples / quickconnect . js
As the ` freeice ` module generates ice servers in a list compliant with the
WebRTC spec you will be able to use it with raw ` RTCPeerConnection `
constructors and other WebRTC libraries .
# # Hey , don ' t use my STUN / TURN server !
If for some reason your free STUN or TURN server ends up in the
list of servers ( [ stun ] ( https : //github.com/DamonOehlman/freeice/blob/master/stun.json) or
[ turn ] ( https : //github.com/DamonOehlman/freeice/blob/master/turn.json))
that is used in this module , you can feel
free to open an issue on this repository and those servers will be removed
within 24 hours ( or sooner ) . This is the quickest and probably the most
polite way to have something removed ( and provides us some visibility
if someone opens a pull request requesting that a server is added ) .
# # Please add my server !
If you have a server that you wish to add to the list , that 's awesome! I' m
sure I speak on behalf of a whole pile of WebRTC developers who say thanks .
To get it into the list , feel free to either open a pull request or if you
find that process a bit daunting then just create an issue requesting
the addition of the server ( make sure you provide all the details , and if
you have a Terms of Service then including that in the PR / issue would be
awesome ) .
# # I know of a free server , can I add it ?
Sure , if you do your homework and make sure it is ok to use ( I ' m currently
in the process of reviewing the terms of those STUN servers included from
the original list ) . If it ' s ok to go , then please see the previous entry
for how to add it .
# # Current List of Servers
* current as at the time of last ` README.md ` file generation
# # # STUN
<< < stun . json
# # # TURN
<< < turn . json
* * /
var freeice = module . exports = function ( opts ) {
// if a list of servers has been provided, then use it instead of defaults
var servers = {
stun : ( opts || { } ) . stun || require ( './stun.json' ) ,
turn : ( opts || { } ) . turn || require ( './turn.json' )
} ;
var stunCount = ( opts || { } ) . stunCount || 2 ;
var turnCount = ( opts || { } ) . turnCount || 0 ;
var selected ;
function getServers ( type , count ) {
var out = [ ] ;
var input = [ ] . concat ( servers [ type ] ) ;
var idx ;
while ( input . length && out . length < count ) {
idx = ( Math . random ( ) * input . length ) | 0 ;
out = out . concat ( input . splice ( idx , 1 ) ) ;
}
return out . map ( function ( url ) {
//If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
if ( ( typeof url !== 'string' ) && ( ! ( url instanceof String ) ) ) {
return url ;
} else {
return normalice ( type + ':' + url ) ;
}
} ) ;
}
// add stun servers
selected = [ ] . concat ( getServers ( 'stun' , stunCount ) ) ;
if ( turnCount ) {
selected = selected . concat ( getServers ( 'turn' , turnCount ) ) ;
}
return selected ;
} ;
} , { "./stun.json" : 6 , "./turn.json" : 7 , "normalice" : 12 } ] , 6 : [ function ( require , module , exports ) {
module . exports = [
]
} , { } ] , 7 : [ function ( require , module , exports ) {
module . exports = [ ]
} , { } ] , 8 : [ function ( require , module , exports ) {
var WildEmitter = require ( 'wildemitter' ) ;
function getMaxVolume ( analyser , fftBins ) {
var maxVolume = - Infinity ;
analyser . getFloatFrequencyData ( fftBins ) ;
for ( var i = 4 , ii = fftBins . length ; i < ii ; i ++ ) {
if ( fftBins [ i ] > maxVolume && fftBins [ i ] < 0 ) {
maxVolume = fftBins [ i ] ;
}
} ;
return maxVolume ;
}
var audioContextType = window . AudioContext || window . webkitAudioContext ;
// use a single audio context due to hardware limits
var audioContext = null ;
module . exports = function ( stream , options ) {
var harker = new WildEmitter ( ) ;
// make it not break in non-supported browsers
if ( ! audioContextType ) return harker ;
//Config
var options = options || { } ,
smoothing = ( options . smoothing || 0.1 ) ,
interval = ( options . interval || 50 ) ,
threshold = options . threshold ,
play = options . play ,
history = options . history || 10 ,
running = true ;
//Setup Audio Context
if ( ! audioContext ) {
audioContext = new audioContextType ( ) ;
}
var sourceNode , fftBins , analyser ;
analyser = audioContext . createAnalyser ( ) ;
analyser . fftSize = 512 ;
analyser . smoothingTimeConstant = smoothing ;
fftBins = new Float32Array ( analyser . fftSize ) ;
if ( stream . jquery ) stream = stream [ 0 ] ;
if ( stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement ) {
//Audio Tag
sourceNode = audioContext . createMediaElementSource ( stream ) ;
if ( typeof play === 'undefined' ) play = true ;
threshold = threshold || - 50 ;
} else {
//WebRTC Stream
sourceNode = audioContext . createMediaStreamSource ( stream ) ;
threshold = threshold || - 50 ;
}
sourceNode . connect ( analyser ) ;
if ( play ) analyser . connect ( audioContext . destination ) ;
harker . speaking = false ;
harker . setThreshold = function ( t ) {
threshold = t ;
} ;
harker . setInterval = function ( i ) {
interval = i ;
} ;
2018-08-30 03:12:34 +08:00
2018-01-16 04:15:58 +08:00
harker . stop = function ( ) {
running = false ;
harker . emit ( 'volume_change' , - 100 , threshold ) ;
if ( harker . speaking ) {
harker . speaking = false ;
harker . emit ( 'stopped_speaking' ) ;
}
} ;
harker . speakingHistory = [ ] ;
for ( var i = 0 ; i < history ; i ++ ) {
harker . speakingHistory . push ( 0 ) ;
}
// Poll the analyser node to determine if speaking
// and emit events if changed
var looper = function ( ) {
setTimeout ( function ( ) {
2018-08-30 03:12:34 +08:00
2018-01-16 04:15:58 +08:00
//check if stop has been called
if ( ! running ) {
return ;
}
2018-08-30 03:12:34 +08:00
2018-01-16 04:15:58 +08:00
var currentVolume = getMaxVolume ( analyser , fftBins ) ;
harker . emit ( 'volume_change' , currentVolume , threshold ) ;
var history = 0 ;
if ( currentVolume > threshold && ! harker . speaking ) {
// trigger quickly, short history
for ( var i = harker . speakingHistory . length - 3 ; i < harker . speakingHistory . length ; i ++ ) {
history += harker . speakingHistory [ i ] ;
}
if ( history >= 2 ) {
harker . speaking = true ;
harker . emit ( 'speaking' ) ;
}
} else if ( currentVolume < threshold && harker . speaking ) {
for ( var i = 0 ; i < harker . speakingHistory . length ; i ++ ) {
history += harker . speakingHistory [ i ] ;
}
if ( history == 0 ) {
harker . speaking = false ;
harker . emit ( 'stopped_speaking' ) ;
}
}
harker . speakingHistory . shift ( ) ;
harker . speakingHistory . push ( 0 + ( currentVolume > threshold ) ) ;
looper ( ) ;
} , interval ) ;
} ;
looper ( ) ;
return harker ;
}
} , { "wildemitter" : 24 } ] , 9 : [ function ( require , module , exports ) {
if ( typeof Object . create === 'function' ) {
// implementation from standard node.js 'util' module
module . exports = function inherits ( ctor , superCtor ) {
ctor . super _ = superCtor
ctor . prototype = Object . create ( superCtor . prototype , {
constructor : {
value : ctor ,
enumerable : false ,
writable : true ,
configurable : true
}
} ) ;
} ;
} else {
// old school shim for old browsers
module . exports = function inherits ( ctor , superCtor ) {
ctor . super _ = superCtor
var TempCtor = function ( ) { }
TempCtor . prototype = superCtor . prototype
ctor . prototype = new TempCtor ( )
ctor . prototype . constructor = ctor
}
}
} , { } ] , 10 : [ function ( require , module , exports ) {
// Does nothing at all.
} , { } ] , 11 : [ function ( require , module , exports ) {
2019-01-11 05:43:30 +08:00
/ * !
2021-03-19 01:47:36 +08:00
* @ name JavaScript / NodeJS Merge v2 . 1.1
2019-01-11 05:43:30 +08:00
* @ author yeikos
* @ repository https : //github.com/yeikos/js.merge
* Copyright 2014 yeikos - MIT license
* https : //raw.github.com/yeikos/js.merge/master/LICENSE
* /
2021-03-19 01:47:36 +08:00
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
exports . isPlainObject = exports . clone = exports . recursive = exports . merge = exports . main = void 0 ;
module . exports = exports = main ;
exports . default = main ;
function main ( ) {
var items = [ ] ;
for ( var _i = 0 ; _i < arguments . length ; _i ++ ) {
items [ _i ] = arguments [ _i ] ;
}
return merge . apply ( void 0 , items ) ;
}
exports . main = main ;
main . clone = clone ;
main . isPlainObject = isPlainObject ;
main . recursive = recursive ;
function merge ( ) {
var items = [ ] ;
for ( var _i = 0 ; _i < arguments . length ; _i ++ ) {
items [ _i ] = arguments [ _i ] ;
}
return _merge ( items [ 0 ] === true , false , items ) ;
}
exports . merge = merge ;
function recursive ( ) {
var items = [ ] ;
for ( var _i = 0 ; _i < arguments . length ; _i ++ ) {
items [ _i ] = arguments [ _i ] ;
}
return _merge ( items [ 0 ] === true , true , items ) ;
}
exports . recursive = recursive ;
function clone ( input ) {
if ( Array . isArray ( input ) ) {
var output = [ ] ;
for ( var index = 0 ; index < input . length ; ++ index )
output . push ( clone ( input [ index ] ) ) ;
return output ;
}
else if ( isPlainObject ( input ) ) {
var output = { } ;
for ( var index in input )
output [ index ] = clone ( input [ index ] ) ;
return output ;
}
else {
return input ;
}
}
exports . clone = clone ;
function isPlainObject ( input ) {
return input && typeof input === 'object' && ! Array . isArray ( input ) ;
}
exports . isPlainObject = isPlainObject ;
function _recursiveMerge ( base , extend ) {
if ( ! isPlainObject ( base ) )
return extend ;
for ( var key in extend ) {
if ( key === '__proto__' || key === 'constructor' || key === 'prototype' )
continue ;
base [ key ] = ( isPlainObject ( base [ key ] ) && isPlainObject ( extend [ key ] ) ) ?
_recursiveMerge ( base [ key ] , extend [ key ] ) :
extend [ key ] ;
}
return base ;
}
function _merge ( isClone , isRecursive , items ) {
var result ;
if ( isClone || ! isPlainObject ( result = items . shift ( ) ) )
result = { } ;
for ( var index = 0 ; index < items . length ; ++ index ) {
var item = items [ index ] ;
if ( ! isPlainObject ( item ) )
continue ;
for ( var key in item ) {
if ( key === '__proto__' || key === 'constructor' || key === 'prototype' )
continue ;
var value = isClone ? clone ( item [ key ] ) : item [ key ] ;
result [ key ] = isRecursive ? _recursiveMerge ( result [ key ] , value ) : value ;
}
}
return result ;
}
2019-01-11 05:43:30 +08:00
2018-01-16 04:15:58 +08:00
} , { } ] , 12 : [ function ( require , module , exports ) {
/ * *
# normalice
Normalize an ice server configuration object ( or plain old string ) into a format
that is usable in all browsers supporting WebRTC . Primarily this module is designed
to help with the transition of the ` url ` attribute of the configuration object to
the ` urls ` attribute .
# # Example Usage
<< < examples / simple . js
* * /
var protocols = [
'stun:' ,
'turn:'
] ;
module . exports = function ( input ) {
var url = ( input || { } ) . url || input ;
var protocol ;
var parts ;
var output = { } ;
// if we don't have a string url, then allow the input to passthrough
if ( typeof url != 'string' && ( ! ( url instanceof String ) ) ) {
return input ;
}
// trim the url string, and convert to an array
url = url . trim ( ) ;
// if the protocol is not known, then passthrough
protocol = protocols [ protocols . indexOf ( url . slice ( 0 , 5 ) ) ] ;
if ( ! protocol ) {
return input ;
}
// now let's attack the remaining url parts
url = url . slice ( 5 ) ;
parts = url . split ( '@' ) ;
output . username = input . username ;
output . credential = input . credential ;
// if we have an authentication part, then set the credentials
if ( parts . length > 1 ) {
url = parts [ 1 ] ;
parts = parts [ 0 ] . split ( ':' ) ;
// add the output credential and username
output . username = parts [ 0 ] ;
output . credential = ( input || { } ) . credential || parts [ 1 ] || '' ;
}
output . url = protocol + url ;
output . urls = [ output . url ] ;
return output ;
} ;
} , { } ] , 13 : [ function ( require , module , exports ) {
var grammar = module . exports = {
v : [ {
name : 'version' ,
reg : /^(\d*)$/
} ] ,
o : [ { //o=- 20518 0 IN IP4 203.0.113.1
// NB: sessionId will be a String in most cases because it is huge
name : 'origin' ,
reg : /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/ ,
names : [ 'username' , 'sessionId' , 'sessionVersion' , 'netType' , 'ipVer' , 'address' ] ,
format : "%s %s %d %s IP%d %s"
} ] ,
// default parsing of these only (though some of these feel outdated)
s : [ { name : 'name' } ] ,
i : [ { name : 'description' } ] ,
u : [ { name : 'uri' } ] ,
e : [ { name : 'email' } ] ,
p : [ { name : 'phone' } ] ,
z : [ { name : 'timezones' } ] , // TODO: this one can actually be parsed properly..
r : [ { name : 'repeats' } ] , // TODO: this one can also be parsed properly
//k: [{}], // outdated thing ignored
t : [ { //t=0 0
name : 'timing' ,
reg : /^(\d*) (\d*)/ ,
names : [ 'start' , 'stop' ] ,
format : "%d %d"
} ] ,
c : [ { //c=IN IP4 10.47.197.26
name : 'connection' ,
reg : /^IN IP(\d) (\S*)/ ,
names : [ 'version' , 'ip' ] ,
format : "IN IP%d %s"
} ] ,
b : [ { //b=AS:4000
push : 'bandwidth' ,
reg : /^(TIAS|AS|CT|RR|RS):(\d*)/ ,
names : [ 'type' , 'limit' ] ,
format : "%s:%s"
} ] ,
m : [ { //m=video 51744 RTP/AVP 126 97 98 34 31
// NB: special - pushes to session
// TODO: rtp/fmtp should be filtered by the payloads found here?
reg : /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/ ,
names : [ 'type' , 'port' , 'protocol' , 'payloads' ] ,
format : "%s %d %s %s"
} ] ,
a : [
{ //a=rtpmap:110 opus/48000/2
push : 'rtp' ,
reg : /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/ ,
names : [ 'payload' , 'codec' , 'rate' , 'encoding' ] ,
format : function ( o ) {
return ( o . encoding ) ?
"rtpmap:%d %s/%s/%s" :
o . rate ?
"rtpmap:%d %s/%s" :
"rtpmap:%d %s" ;
}
} ,
{
//a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
//a=fmtp:111 minptime=10; useinbandfec=1
push : 'fmtp' ,
reg : /^fmtp:(\d*) ([\S| ]*)/ ,
names : [ 'payload' , 'config' ] ,
format : "fmtp:%d %s"
} ,
{ //a=control:streamid=0
name : 'control' ,
reg : /^control:(.*)/ ,
format : "control:%s"
} ,
{ //a=rtcp:65179 IN IP4 193.84.77.194
name : 'rtcp' ,
reg : /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/ ,
names : [ 'port' , 'netType' , 'ipVer' , 'address' ] ,
format : function ( o ) {
return ( o . address != null ) ?
"rtcp:%d %s IP%d %s" :
"rtcp:%d" ;
}
} ,
{ //a=rtcp-fb:98 trr-int 100
push : 'rtcpFbTrrInt' ,
reg : /^rtcp-fb:(\*|\d*) trr-int (\d*)/ ,
names : [ 'payload' , 'value' ] ,
format : "rtcp-fb:%d trr-int %d"
} ,
{ //a=rtcp-fb:98 nack rpsi
push : 'rtcpFb' ,
reg : /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/ ,
names : [ 'payload' , 'type' , 'subtype' ] ,
format : function ( o ) {
return ( o . subtype != null ) ?
"rtcp-fb:%s %s %s" :
"rtcp-fb:%s %s" ;
}
} ,
{ //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
//a=extmap:1/recvonly URI-gps-string
push : 'ext' ,
reg : /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/ ,
names : [ 'value' , 'uri' , 'config' ] , // value may include "/direction" suffix
format : function ( o ) {
return ( o . config != null ) ?
"extmap:%s %s %s" :
"extmap:%s %s" ;
}
} ,
{
//a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
push : 'crypto' ,
reg : /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/ ,
names : [ 'id' , 'suite' , 'config' , 'sessionConfig' ] ,
format : function ( o ) {
return ( o . sessionConfig != null ) ?
"crypto:%d %s %s %s" :
"crypto:%d %s %s" ;
}
} ,
{ //a=setup:actpass
name : 'setup' ,
reg : /^setup:(\w*)/ ,
format : "setup:%s"
} ,
{ //a=mid:1
name : 'mid' ,
reg : /^mid:([^\s]*)/ ,
format : "mid:%s"
} ,
{ //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
name : 'msid' ,
reg : /^msid:(.*)/ ,
format : "msid:%s"
} ,
{ //a=ptime:20
name : 'ptime' ,
reg : /^ptime:(\d*)/ ,
format : "ptime:%d"
} ,
{ //a=maxptime:60
name : 'maxptime' ,
reg : /^maxptime:(\d*)/ ,
format : "maxptime:%d"
} ,
{ //a=sendrecv
name : 'direction' ,
reg : /^(sendrecv|recvonly|sendonly|inactive)/
} ,
{ //a=ice-lite
name : 'icelite' ,
reg : /^(ice-lite)/
} ,
{ //a=ice-ufrag:F7gI
name : 'iceUfrag' ,
reg : /^ice-ufrag:(\S*)/ ,
format : "ice-ufrag:%s"
} ,
{ //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
name : 'icePwd' ,
reg : /^ice-pwd:(\S*)/ ,
format : "ice-pwd:%s"
} ,
{ //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
name : 'fingerprint' ,
reg : /^fingerprint:(\S*) (\S*)/ ,
names : [ 'type' , 'hash' ] ,
format : "fingerprint:%s %s"
} ,
{
//a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
//a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
//a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
//a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0
//a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0
push : 'candidates' ,
reg : /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/ ,
names : [ 'foundation' , 'component' , 'transport' , 'priority' , 'ip' , 'port' , 'type' , 'raddr' , 'rport' , 'tcptype' , 'generation' ] ,
format : function ( o ) {
var str = "candidate:%s %d %s %d %s %d typ %s" ;
str += ( o . raddr != null ) ? " raddr %s rport %d" : "%v%v" ;
// NB: candidate has three optional chunks, so %void middles one if it's missing
str += ( o . tcptype != null ) ? " tcptype %s" : "%v" ;
if ( o . generation != null ) {
str += " generation %d" ;
}
return str ;
}
} ,
{ //a=end-of-candidates (keep after the candidates line for readability)
name : 'endOfCandidates' ,
reg : /^(end-of-candidates)/
} ,
{ //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
name : 'remoteCandidates' ,
reg : /^remote-candidates:(.*)/ ,
format : "remote-candidates:%s"
} ,
{ //a=ice-options:google-ice
name : 'iceOptions' ,
reg : /^ice-options:(\S*)/ ,
format : "ice-options:%s"
} ,
{ //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
push : "ssrcs" ,
reg : /^ssrc:(\d*) ([\w_]*):(.*)/ ,
names : [ 'id' , 'attribute' , 'value' ] ,
format : "ssrc:%d %s:%s"
} ,
{ //a=ssrc-group:FEC 1 2
push : "ssrcGroups" ,
reg : /^ssrc-group:(\w*) (.*)/ ,
names : [ 'semantics' , 'ssrcs' ] ,
format : "ssrc-group:%s %s"
} ,
{ //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
name : "msidSemantic" ,
reg : /^msid-semantic:\s?(\w*) (\S*)/ ,
names : [ 'semantic' , 'token' ] ,
format : "msid-semantic: %s %s" // space after ":" is not accidental
} ,
{ //a=group:BUNDLE audio video
push : 'groups' ,
reg : /^group:(\w*) (.*)/ ,
names : [ 'type' , 'mids' ] ,
format : "group:%s %s"
} ,
{ //a=rtcp-mux
name : 'rtcpMux' ,
reg : /^(rtcp-mux)/
} ,
{ //a=rtcp-rsize
name : 'rtcpRsize' ,
reg : /^(rtcp-rsize)/
} ,
{ // any a= that we don't understand is kepts verbatim on media.invalid
push : 'invalid' ,
names : [ "value" ]
}
]
} ;
// set sensible defaults to avoid polluting the grammar with boring details
Object . keys ( grammar ) . forEach ( function ( key ) {
var objs = grammar [ key ] ;
objs . forEach ( function ( obj ) {
if ( ! obj . reg ) {
obj . reg = /(.*)/ ;
}
if ( ! obj . format ) {
obj . format = "%s" ;
}
} ) ;
} ) ;
} , { } ] , 14 : [ function ( require , module , exports ) {
var parser = require ( './parser' ) ;
var writer = require ( './writer' ) ;
exports . write = writer ;
exports . parse = parser . parse ;
exports . parseFmtpConfig = parser . parseFmtpConfig ;
exports . parsePayloads = parser . parsePayloads ;
exports . parseRemoteCandidates = parser . parseRemoteCandidates ;
} , { "./parser" : 15 , "./writer" : 16 } ] , 15 : [ function ( require , module , exports ) {
var toIntIfInt = function ( v ) {
return String ( Number ( v ) ) === v ? Number ( v ) : v ;
} ;
var attachProperties = function ( match , location , names , rawName ) {
if ( rawName && ! names ) {
location [ rawName ] = toIntIfInt ( match [ 1 ] ) ;
}
else {
for ( var i = 0 ; i < names . length ; i += 1 ) {
if ( match [ i + 1 ] != null ) {
location [ names [ i ] ] = toIntIfInt ( match [ i + 1 ] ) ;
}
}
}
} ;
var parseReg = function ( obj , location , content ) {
var needsBlank = obj . name && obj . names ;
if ( obj . push && ! location [ obj . push ] ) {
location [ obj . push ] = [ ] ;
}
else if ( needsBlank && ! location [ obj . name ] ) {
location [ obj . name ] = { } ;
}
var keyLocation = obj . push ?
{ } : // blank object that will be pushed
needsBlank ? location [ obj . name ] : location ; // otherwise, named location or root
attachProperties ( content . match ( obj . reg ) , keyLocation , obj . names , obj . name ) ;
if ( obj . push ) {
location [ obj . push ] . push ( keyLocation ) ;
}
} ;
var grammar = require ( './grammar' ) ;
var validLine = RegExp . prototype . test . bind ( /^([a-z])=(.*)/ ) ;
exports . parse = function ( sdp ) {
var session = { }
, media = [ ]
, location = session ; // points at where properties go under (one of the above)
// parse lines we understand
sdp . split ( /(\r\n|\r|\n)/ ) . filter ( validLine ) . forEach ( function ( l ) {
var type = l [ 0 ] ;
var content = l . slice ( 2 ) ;
if ( type === 'm' ) {
media . push ( { rtp : [ ] , fmtp : [ ] } ) ;
location = media [ media . length - 1 ] ; // point at latest media line
}
for ( var j = 0 ; j < ( grammar [ type ] || [ ] ) . length ; j += 1 ) {
var obj = grammar [ type ] [ j ] ;
if ( obj . reg . test ( content ) ) {
return parseReg ( obj , location , content ) ;
}
}
} ) ;
session . media = media ; // link it up
return session ;
} ;
var fmtpReducer = function ( acc , expr ) {
var s = expr . split ( '=' ) ;
if ( s . length === 2 ) {
acc [ s [ 0 ] ] = toIntIfInt ( s [ 1 ] ) ;
}
return acc ;
} ;
exports . parseFmtpConfig = function ( str ) {
return str . split ( /\;\s?/ ) . reduce ( fmtpReducer , { } ) ;
} ;
exports . parsePayloads = function ( str ) {
return str . split ( ' ' ) . map ( Number ) ;
} ;
exports . parseRemoteCandidates = function ( str ) {
var candidates = [ ] ;
var parts = str . split ( ' ' ) . map ( toIntIfInt ) ;
for ( var i = 0 ; i < parts . length ; i += 3 ) {
candidates . push ( {
component : parts [ i ] ,
ip : parts [ i + 1 ] ,
port : parts [ i + 2 ]
} ) ;
}
return candidates ;
} ;
} , { "./grammar" : 13 } ] , 16 : [ function ( require , module , exports ) {
var grammar = require ( './grammar' ) ;
// customized util.format - discards excess arguments and can void middle ones
var formatRegExp = /%[sdv%]/g ;
var format = function ( formatStr ) {
var i = 1 ;
var args = arguments ;
var len = args . length ;
return formatStr . replace ( formatRegExp , function ( x ) {
if ( i >= len ) {
return x ; // missing argument
}
var arg = args [ i ] ;
i += 1 ;
switch ( x ) {
case '%%' :
return '%' ;
case '%s' :
return String ( arg ) ;
case '%d' :
return Number ( arg ) ;
case '%v' :
return '' ;
}
} ) ;
// NB: we discard excess arguments - they are typically undefined from makeLine
} ;
var makeLine = function ( type , obj , location ) {
var str = obj . format instanceof Function ?
( obj . format ( obj . push ? location : location [ obj . name ] ) ) :
obj . format ;
var args = [ type + '=' + str ] ;
if ( obj . names ) {
for ( var i = 0 ; i < obj . names . length ; i += 1 ) {
var n = obj . names [ i ] ;
if ( obj . name ) {
args . push ( location [ obj . name ] [ n ] ) ;
}
else { // for mLine and push attributes
args . push ( location [ obj . names [ i ] ] ) ;
}
}
}
else {
args . push ( location [ obj . name ] ) ;
}
return format . apply ( null , args ) ;
} ;
// RFC specified order
// TODO: extend this with all the rest
var defaultOuterOrder = [
'v' , 'o' , 's' , 'i' ,
'u' , 'e' , 'p' , 'c' ,
'b' , 't' , 'r' , 'z' , 'a'
] ;
var defaultInnerOrder = [ 'i' , 'c' , 'b' , 'a' ] ;
module . exports = function ( session , opts ) {
opts = opts || { } ;
// ensure certain properties exist
if ( session . version == null ) {
session . version = 0 ; // "v=0" must be there (only defined version atm)
}
if ( session . name == null ) {
session . name = " " ; // "s= " must be there if no meaningful name set
}
session . media . forEach ( function ( mLine ) {
if ( mLine . payloads == null ) {
mLine . payloads = "" ;
}
} ) ;
var outerOrder = opts . outerOrder || defaultOuterOrder ;
var innerOrder = opts . innerOrder || defaultInnerOrder ;
var sdp = [ ] ;
// loop through outerOrder for matching properties on session
outerOrder . forEach ( function ( type ) {
grammar [ type ] . forEach ( function ( obj ) {
if ( obj . name in session && session [ obj . name ] != null ) {
sdp . push ( makeLine ( type , obj , session ) ) ;
}
else if ( obj . push in session && session [ obj . push ] != null ) {
session [ obj . push ] . forEach ( function ( el ) {
sdp . push ( makeLine ( type , obj , el ) ) ;
} ) ;
}
} ) ;
} ) ;
// then for each media line, follow the innerOrder
session . media . forEach ( function ( mLine ) {
sdp . push ( makeLine ( 'm' , grammar . m [ 0 ] , mLine ) ) ;
innerOrder . forEach ( function ( type ) {
grammar [ type ] . forEach ( function ( obj ) {
if ( obj . name in mLine && mLine [ obj . name ] != null ) {
sdp . push ( makeLine ( type , obj , mLine ) ) ;
}
else if ( obj . push in mLine && mLine [ obj . push ] != null ) {
mLine [ obj . push ] . forEach ( function ( el ) {
sdp . push ( makeLine ( type , obj , el ) ) ;
} ) ;
}
} ) ;
} ) ;
} ) ;
return sdp . join ( '\r\n' ) + '\r\n' ;
} ;
} , { "./grammar" : 13 } ] , 17 : [ function ( require , module , exports ) {
/ * C o p y r i g h t @ 2 0 1 5 A t l a s s i a n P t y L t d
*
* 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 .
* /
module . exports = function arrayEquals ( array ) {
// if the other array is a falsy value, return
if ( ! array )
return false ;
// compare lengths - can save a lot of time
if ( this . length != array . length )
return false ;
for ( var i = 0 , l = this . length ; i < l ; i ++ ) {
// Check if we have nested arrays
if ( this [ i ] instanceof Array && array [ i ] instanceof Array ) {
// recurse into the nested arrays
if ( ! arrayEquals . apply ( this [ i ] , [ array [ i ] ] ) )
return false ;
} else if ( this [ i ] != array [ i ] ) {
// Warning - two different object instances will never be equal:
// {x:20} != {x:20}
return false ;
}
}
return true ;
} ;
} , { } ] , 18 : [ function ( require , module , exports ) {
/ * C o p y r i g h t @ 2 0 1 5 A t l a s s i a n P t y L t d
*
* 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 .
* /
exports . Interop = require ( './interop' ) ;
} , { "./interop" : 19 } ] , 19 : [ function ( require , module , exports ) {
/ * C o p y r i g h t @ 2 0 1 5 A t l a s s i a n P t y L t d
*
* 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 .
* /
/* global RTCSessionDescription */
/* global RTCIceCandidate */
/* jshint -W097 */
"use strict" ;
var transform = require ( './transform' ) ;
var arrayEquals = require ( './array-equals' ) ;
function Interop ( ) {
/ * *
* This map holds the most recent Unified Plan offer / answer SDP that was
* converted to Plan B , with the SDP type ( 'offer' or 'answer' ) as keys and
* the SDP string as values .
*
* @ type { { } }
* /
this . cache = {
mlB2UMap : { } ,
mlU2BMap : { }
} ;
}
module . exports = Interop ;
/ * *
* Changes the candidate args to match with the related Unified Plan
* /
Interop . prototype . candidateToUnifiedPlan = function ( candidate ) {
var cand = new RTCIceCandidate ( candidate ) ;
cand . sdpMLineIndex = this . cache . mlB2UMap [ cand . sdpMLineIndex ] ;
/* TODO: change sdpMid to (audio|video)-SSRC */
return cand ;
} ;
/ * *
* Changes the candidate args to match with the related Plan B
* /
Interop . prototype . candidateToPlanB = function ( candidate ) {
var cand = new RTCIceCandidate ( candidate ) ;
if ( cand . sdpMid . indexOf ( 'audio' ) === 0 ) {
cand . sdpMid = 'audio' ;
} else if ( cand . sdpMid . indexOf ( 'video' ) === 0 ) {
cand . sdpMid = 'video' ;
} else {
throw new Error ( 'candidate with ' + cand . sdpMid + ' not allowed' ) ;
}
cand . sdpMLineIndex = this . cache . mlU2BMap [ cand . sdpMLineIndex ] ;
return cand ;
} ;
/ * *
* Returns the index of the first m - line with the given media type and with a
* direction which allows sending , in the last Unified Plan description with
* type "answer" converted to Plan B . Returns { null } if there is no saved
* answer , or if none of its m - lines with the given type allow sending .
* @ param type the media type ( "audio" or "video" ) .
* @ returns { * }
* /
Interop . prototype . getFirstSendingIndexFromAnswer = function ( type ) {
if ( ! this . cache . answer ) {
return null ;
}
var session = transform . parse ( this . cache . answer ) ;
if ( session && session . media && Array . isArray ( session . media ) ) {
for ( var i = 0 ; i < session . media . length ; i ++ ) {
if ( session . media [ i ] . type == type &&
( ! session . media [ i ] . direction /* default to sendrecv */ ||
session . media [ i ] . direction === 'sendrecv' ||
session . media [ i ] . direction === 'sendonly' ) ) {
return i ;
}
}
}
return null ;
} ;
/ * *
* This method transforms a Unified Plan SDP to an equivalent Plan B SDP . A
* PeerConnection wrapper transforms the SDP to Plan B before passing it to the
* application .
*
* @ param desc
* @ returns { * }
* /
Interop . prototype . toPlanB = function ( desc ) {
var self = this ;
//#region Preliminary input validation.
if ( typeof desc !== 'object' || desc === null ||
typeof desc . sdp !== 'string' ) {
console . warn ( 'An empty description was passed as an argument.' ) ;
return desc ;
}
// Objectify the SDP for easier manipulation.
var session = transform . parse ( desc . sdp ) ;
// If the SDP contains no media, there's nothing to transform.
if ( typeof session . media === 'undefined' ||
! Array . isArray ( session . media ) || session . media . length === 0 ) {
console . warn ( 'The description has no media.' ) ;
return desc ;
}
// Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
// SDP has a video, an audio and a data "channel" at most.
if ( session . media . length <= 3 && session . media . every ( function ( m ) {
return [ 'video' , 'audio' , 'data' ] . indexOf ( m . mid ) !== - 1 ;
} ) ) {
console . warn ( 'This description does not look like Unified Plan.' ) ;
return desc ;
}
//#endregion
// HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
var sdp = desc . sdp ;
var rewrite = false ;
for ( var i = 0 ; i < session . media . length ; i ++ ) {
var uLine = session . media [ i ] ;
uLine . rtp . forEach ( function ( rtp ) {
if ( rtp . codec === 'NULL' )
{
rewrite = true ;
var offer = transform . parse ( self . cache . offer ) ;
rtp . codec = offer . media [ i ] . rtp [ 0 ] . codec ;
}
} ) ;
}
if ( rewrite ) {
sdp = transform . write ( session ) ;
}
// Unified Plan SDP is our "precious". Cache it for later use in the Plan B
// -> Unified Plan transformation.
this . cache [ desc . type ] = sdp ;
//#region Convert from Unified Plan to Plan B.
// We rebuild the session.media array.
var media = session . media ;
session . media = [ ] ;
// Associative array that maps channel types to channel objects for fast
// access to channel objects by their type, e.g. type2bl['audio']->channel
// obj.
var type2bl = { } ;
// Used to build the group:BUNDLE value after the channels construction
// loop.
var types = [ ] ;
media . forEach ( function ( uLine ) {
// rtcp-mux is required in the Plan B SDP.
if ( ( typeof uLine . rtcpMux !== 'string' ||
uLine . rtcpMux !== 'rtcp-mux' ) &&
uLine . direction !== 'inactive' ) {
throw new Error ( 'Cannot convert to Plan B because m-lines ' +
'without the rtcp-mux attribute were found.' ) ;
}
// If we don't have a channel for this uLine.type OR the selected is
// inactive, then select this uLine as the channel basis.
if ( typeof type2bl [ uLine . type ] === 'undefined' ||
type2bl [ uLine . type ] . direction === 'inactive' ) {
type2bl [ uLine . type ] = uLine ;
}
if ( uLine . protocol != type2bl [ uLine . type ] . protocol ) {
throw new Error ( 'Cannot convert to Plan B because m-lines ' +
'have different protocols and this library does not have ' +
'support for that' ) ;
}
if ( uLine . payloads != type2bl [ uLine . type ] . payloads ) {
throw new Error ( 'Cannot convert to Plan B because m-lines ' +
'have different payloads and this library does not have ' +
'support for that' ) ;
}
} ) ;
// Implode the Unified Plan m-lines/tracks into Plan B channels.
media . forEach ( function ( uLine ) {
if ( uLine . type === 'application' ) {
session . media . push ( uLine ) ;
types . push ( uLine . mid ) ;
return ;
}
// Add sources to the channel and handle a=msid.
if ( typeof uLine . sources === 'object' ) {
Object . keys ( uLine . sources ) . forEach ( function ( ssrc ) {
if ( typeof type2bl [ uLine . type ] . sources !== 'object' )
type2bl [ uLine . type ] . sources = { } ;
// Assign the sources to the channel.
type2bl [ uLine . type ] . sources [ ssrc ] =
uLine . sources [ ssrc ] ;
if ( typeof uLine . msid !== 'undefined' ) {
// In Plan B the msid is an SSRC attribute. Also, we don't
// care about the obsolete label and mslabel attributes.
//
// Note that it is not guaranteed that the uLine will
// have an msid. recvonly channels in particular don't have
// one.
type2bl [ uLine . type ] . sources [ ssrc ] . msid =
uLine . msid ;
}
// NOTE ssrcs in ssrc groups will share msids, as
// draft-uberti-rtcweb-plan-00 mandates.
} ) ;
}
// Add ssrc groups to the channel.
if ( typeof uLine . ssrcGroups !== 'undefined' &&
Array . isArray ( uLine . ssrcGroups ) ) {
// Create the ssrcGroups array, if it's not defined.
if ( typeof type2bl [ uLine . type ] . ssrcGroups === 'undefined' ||
! Array . isArray ( type2bl [ uLine . type ] . ssrcGroups ) ) {
type2bl [ uLine . type ] . ssrcGroups = [ ] ;
}
type2bl [ uLine . type ] . ssrcGroups =
type2bl [ uLine . type ] . ssrcGroups . concat (
uLine . ssrcGroups ) ;
}
if ( type2bl [ uLine . type ] === uLine ) {
// Plan B mids are in ['audio', 'video', 'data']
uLine . mid = uLine . type ;
// Plan B doesn't support/need the bundle-only attribute.
delete uLine . bundleOnly ;
// In Plan B the msid is an SSRC attribute.
delete uLine . msid ;
if ( uLine . type == media [ 0 ] . type ) {
types . unshift ( uLine . type ) ;
// Add the channel to the new media array.
session . media . unshift ( uLine ) ;
} else {
types . push ( uLine . type ) ;
// Add the channel to the new media array.
session . media . push ( uLine ) ;
}
}
} ) ;
if ( typeof session . groups !== 'undefined' ) {
// We regenerate the BUNDLE group with the new mids.
session . groups . some ( function ( group ) {
if ( group . type === 'BUNDLE' ) {
group . mids = types . join ( ' ' ) ;
return true ;
}
} ) ;
}
// msid semantic
session . msidSemantic = {
semantic : 'WMS' ,
token : '*'
} ;
var resStr = transform . write ( session ) ;
return new RTCSessionDescription ( {
type : desc . type ,
sdp : resStr
} ) ;
//#endregion
} ;
/* follow rules defined in RFC4145 */
function addSetupAttr ( uLine ) {
if ( typeof uLine . setup === 'undefined' ) {
return ;
}
if ( uLine . setup === "active" ) {
uLine . setup = "passive" ;
} else if ( uLine . setup === "passive" ) {
uLine . setup = "active" ;
}
}
/ * *
* This method transforms a Plan B SDP to an equivalent Unified Plan SDP . A
* PeerConnection wrapper transforms the SDP to Unified Plan before passing it
* to FF .
*
* @ param desc
* @ returns { * }
* /
Interop . prototype . toUnifiedPlan = function ( desc ) {
var self = this ;
//#region Preliminary input validation.
if ( typeof desc !== 'object' || desc === null ||
typeof desc . sdp !== 'string' ) {
console . warn ( 'An empty description was passed as an argument.' ) ;
return desc ;
}
var session = transform . parse ( desc . sdp ) ;
// If the SDP contains no media, there's nothing to transform.
if ( typeof session . media === 'undefined' ||
! Array . isArray ( session . media ) || session . media . length === 0 ) {
console . warn ( 'The description has no media.' ) ;
return desc ;
}
// Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
// a video, an audio and a data "channel" at most.
if ( session . media . length > 3 || ! session . media . every ( function ( m ) {
return [ 'video' , 'audio' , 'data' ] . indexOf ( m . mid ) !== - 1 ;
} ) ) {
console . warn ( 'This description does not look like Plan B.' ) ;
return desc ;
}
// Make sure this Plan B SDP can be converted to a Unified Plan SDP.
var mids = [ ] ;
session . media . forEach ( function ( m ) {
mids . push ( m . mid ) ;
} ) ;
var hasBundle = false ;
if ( typeof session . groups !== 'undefined' &&
Array . isArray ( session . groups ) ) {
hasBundle = session . groups . every ( function ( g ) {
return g . type !== 'BUNDLE' ||
arrayEquals . apply ( g . mids . sort ( ) , [ mids . sort ( ) ] ) ;
} ) ;
}
if ( ! hasBundle ) {
var mustBeBundle = false ;
session . media . forEach ( function ( m ) {
if ( m . direction !== 'inactive' ) {
mustBeBundle = true ;
}
} ) ;
if ( mustBeBundle ) {
throw new Error ( "Cannot convert to Unified Plan because m-lines that" +
" are not bundled were found." ) ;
}
}
//#endregion
//#region Convert from Plan B to Unified Plan.
// Unfortunately, a Plan B offer/answer doesn't have enough information to
// rebuild an equivalent Unified Plan offer/answer.
//
// For example, if this is a local answer (in Unified Plan style) that we
// convert to Plan B prior to handing it over to the application (the
// PeerConnection wrapper called us, for instance, after a successful
// createAnswer), we want to remember the m-line at which we've seen the
// (local) SSRC. That's because when the application wants to do call the
// SLD method, forcing us to do the inverse transformation (from Plan B to
// Unified Plan), we need to know to which m-line to assign the (local)
// SSRC. We also need to know all the other m-lines that the original
// answer had and include them in the transformed answer as well.
//
// Another example is if this is a remote offer that we convert to Plan B
// prior to giving it to the application, we want to remember the mid at
// which we've seen the (remote) SSRC.
//
// In the iteration that follows, we use the cached Unified Plan (if it
// exists) to assign mids to ssrcs.
var type ;
if ( desc . type === 'answer' ) {
type = 'offer' ;
} else if ( desc . type === 'offer' ) {
type = 'answer' ;
} else {
throw new Error ( "Type '" + desc . type + "' not supported." ) ;
}
var cached ;
if ( typeof this . cache [ type ] !== 'undefined' ) {
cached = transform . parse ( this . cache [ type ] ) ;
}
var recvonlySsrcs = {
audio : { } ,
video : { }
} ;
// A helper map that sends mids to m-line objects. We use it later to
// rebuild the Unified Plan style session.media array.
var mid2ul = { } ;
var bIdx = 0 ;
var uIdx = 0 ;
var sources2ul = { } ;
var candidates ;
var iceUfrag ;
var icePwd ;
var fingerprint ;
var payloads = { } ;
var rtcpFb = { } ;
var rtp = { } ;
session . media . forEach ( function ( bLine ) {
if ( ( typeof bLine . rtcpMux !== 'string' ||
bLine . rtcpMux !== 'rtcp-mux' ) &&
bLine . direction !== 'inactive' ) {
throw new Error ( "Cannot convert to Unified Plan because m-lines " +
"without the rtcp-mux attribute were found." ) ;
}
if ( bLine . type === 'application' ) {
mid2ul [ bLine . mid ] = bLine ;
return ;
}
// With rtcp-mux and bundle all the channels should have the same ICE
// stuff.
var sources = bLine . sources ;
var ssrcGroups = bLine . ssrcGroups ;
var port = bLine . port ;
/* Chrome adds different candidates even using bundle, so we concat the candidates list */
if ( typeof bLine . candidates != 'undefined' ) {
if ( typeof candidates != 'undefined' ) {
candidates = candidates . concat ( bLine . candidates ) ;
} else {
candidates = bLine . candidates ;
}
}
if ( ( typeof iceUfrag != 'undefined' ) && ( typeof bLine . iceUfrag != 'undefined' ) && ( iceUfrag != bLine . iceUfrag ) ) {
throw new Error ( "Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" +
"\tLast iceUfrag: " + iceUfrag + "\n" +
"\tNew iceUfrag: " + bLine . iceUfrag
) ;
}
if ( typeof bLine . iceUfrag != 'undefined' ) {
iceUfrag = bLine . iceUfrag ;
}
if ( ( typeof icePwd != 'undefined' ) && ( typeof bLine . icePwd != 'undefined' ) && ( icePwd != bLine . icePwd ) ) {
throw new Error ( "Only BUNDLE supported, icePwd must be the same for all m-lines.\n" +
"\tLast icePwd: " + icePwd + "\n" +
"\tNew icePwd: " + bLine . icePwd
) ;
}
if ( typeof bLine . icePwd != 'undefined' ) {
icePwd = bLine . icePwd ;
}
if ( ( typeof fingerprint != 'undefined' ) && ( typeof bLine . fingerprint != 'undefined' ) &&
( fingerprint . type != bLine . fingerprint . type || fingerprint . hash != bLine . fingerprint . hash ) ) {
throw new Error ( "Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" +
"\tLast fingerprint: " + JSON . stringify ( fingerprint ) + "\n" +
"\tNew fingerprint: " + JSON . stringify ( bLine . fingerprint )
) ;
}
if ( typeof bLine . fingerprint != 'undefined' ) {
fingerprint = bLine . fingerprint ;
}
payloads [ bLine . type ] = bLine . payloads ;
rtcpFb [ bLine . type ] = bLine . rtcpFb ;
rtp [ bLine . type ] = bLine . rtp ;
// inverted ssrc group map
var ssrc2group = { } ;
if ( typeof ssrcGroups !== 'undefined' && Array . isArray ( ssrcGroups ) ) {
ssrcGroups . forEach ( function ( ssrcGroup ) {
// XXX This might brake if an SSRC is in more than one group
// for some reason.
if ( typeof ssrcGroup . ssrcs !== 'undefined' &&
Array . isArray ( ssrcGroup . ssrcs ) ) {
ssrcGroup . ssrcs . forEach ( function ( ssrc ) {
if ( typeof ssrc2group [ ssrc ] === 'undefined' ) {
ssrc2group [ ssrc ] = [ ] ;
}
ssrc2group [ ssrc ] . push ( ssrcGroup ) ;
} ) ;
}
} ) ;
}
// ssrc to m-line index.
var ssrc2ml = { } ;
if ( typeof sources === 'object' ) {
// We'll use the "bLine" object as a prototype for each new "mLine"
// that we create, but first we need to clean it up a bit.
delete bLine . sources ;
delete bLine . ssrcGroups ;
delete bLine . candidates ;
delete bLine . iceUfrag ;
delete bLine . icePwd ;
delete bLine . fingerprint ;
delete bLine . port ;
delete bLine . mid ;
// Explode the Plan B channel sources with one m-line per source.
Object . keys ( sources ) . forEach ( function ( ssrc ) {
// The (unified) m-line for this SSRC. We either create it from
// scratch or, if it's a grouped SSRC, we re-use a related
// mline. In other words, if the source is grouped with another
// source, put the two together in the same m-line.
var uLine ;
// We assume here that we are the answerer in the O/A, so any
// offers which we translate come from the remote side, while
// answers are local. So the check below is to make that we
// handle receive-only SSRCs in a special way only if they come
// from the remote side.
if ( desc . type === 'offer' ) {
// We want to detect SSRCs which are used by a remote peer
// in an m-line with direction=recvonly (i.e. they are
// being used for RTCP only).
// This information would have gotten lost if the remote
// peer used Unified Plan and their local description was
// translated to Plan B. So we use the lack of an MSID
// attribute to deduce a "receive only" SSRC.
if ( ! sources [ ssrc ] . msid ) {
recvonlySsrcs [ bLine . type ] [ ssrc ] = sources [ ssrc ] ;
// Receive-only SSRCs must not create new m-lines. We
// will assign them to an existing m-line later.
return ;
}
}
if ( typeof ssrc2group [ ssrc ] !== 'undefined' &&
Array . isArray ( ssrc2group [ ssrc ] ) ) {
ssrc2group [ ssrc ] . some ( function ( ssrcGroup ) {
// ssrcGroup.ssrcs *is* an Array, no need to check
// again here.
return ssrcGroup . ssrcs . some ( function ( related ) {
if ( typeof ssrc2ml [ related ] === 'object' ) {
uLine = ssrc2ml [ related ] ;
return true ;
}
} ) ;
} ) ;
}
if ( typeof uLine === 'object' ) {
// the m-line already exists. Just add the source.
uLine . sources [ ssrc ] = sources [ ssrc ] ;
delete sources [ ssrc ] . msid ;
} else {
// Use the "bLine" as a prototype for the "uLine".
uLine = Object . create ( bLine ) ;
ssrc2ml [ ssrc ] = uLine ;
if ( typeof sources [ ssrc ] . msid !== 'undefined' ) {
// Assign the msid of the source to the m-line. Note
// that it is not guaranteed that the source will have
// msid. In particular "recvonly" sources don't have an
// msid. Note that "recvonly" is a term only defined
// for m-lines.
uLine . msid = sources [ ssrc ] . msid ;
delete sources [ ssrc ] . msid ;
}
// We assign one SSRC per media line.
uLine . sources = { } ;
uLine . sources [ ssrc ] = sources [ ssrc ] ;
uLine . ssrcGroups = ssrc2group [ ssrc ] ;
// Use the cached Unified Plan SDP (if it exists) to assign
// SSRCs to mids.
if ( typeof cached !== 'undefined' &&
typeof cached . media !== 'undefined' &&
Array . isArray ( cached . media ) ) {
cached . media . forEach ( function ( m ) {
if ( typeof m . sources === 'object' ) {
Object . keys ( m . sources ) . forEach ( function ( s ) {
if ( s === ssrc ) {
uLine . mid = m . mid ;
}
} ) ;
}
} ) ;
}
if ( typeof uLine . mid === 'undefined' ) {
// If this is an SSRC that we see for the first time
// assign it a new mid. This is typically the case when
// this method is called to transform a remote
// description for the first time or when there is a
// new SSRC in the remote description because a new
// peer has joined the conference. Local SSRCs should
// have already been added to the map in the toPlanB
// method.
//
// Because FF generates answers in Unified Plan style,
// we MUST already have a cached answer with all the
// local SSRCs mapped to some m-line/mid.
uLine . mid = [ bLine . type , '-' , ssrc ] . join ( '' ) ;
}
// Include the candidates in the 1st media line.
uLine . candidates = candidates ;
uLine . iceUfrag = iceUfrag ;
uLine . icePwd = icePwd ;
uLine . fingerprint = fingerprint ;
uLine . port = port ;
mid2ul [ uLine . mid ] = uLine ;
sources2ul [ uIdx ] = uLine . sources ;
self . cache . mlU2BMap [ uIdx ] = bIdx ;
if ( typeof self . cache . mlB2UMap [ bIdx ] === 'undefined' ) {
self . cache . mlB2UMap [ bIdx ] = uIdx ;
}
uIdx ++ ;
}
} ) ;
} else {
var uLine = bLine ;
uLine . candidates = candidates ;
uLine . iceUfrag = iceUfrag ;
uLine . icePwd = icePwd ;
uLine . fingerprint = fingerprint ;
uLine . port = port ;
mid2ul [ uLine . mid ] = uLine ;
self . cache . mlU2BMap [ uIdx ] = bIdx ;
if ( typeof self . cache . mlB2UMap [ bIdx ] === 'undefined' ) {
self . cache . mlB2UMap [ bIdx ] = uIdx ;
}
}
bIdx ++ ;
} ) ;
// Rebuild the media array in the right order and add the missing mLines
// (missing from the Plan B SDP).
session . media = [ ] ;
mids = [ ] ; // reuse
if ( desc . type === 'answer' ) {
// The media lines in the answer must match the media lines in the
// offer. The order is important too. Here we assume that Firefox is
// the answerer, so we merely have to use the reconstructed (unified)
// answer to update the cached (unified) answer accordingly.
//
// In the general case, one would have to use the cached (unified)
// offer to find the m-lines that are missing from the reconstructed
// answer, potentially grabbing them from the cached (unified) answer.
// One has to be careful with this approach because inactive m-lines do
// not always have an mid, making it tricky (impossible?) to find where
// exactly and which m-lines are missing from the reconstructed answer.
for ( var i = 0 ; i < cached . media . length ; i ++ ) {
var uLine = cached . media [ i ] ;
delete uLine . msid ;
delete uLine . sources ;
delete uLine . ssrcGroups ;
if ( typeof sources2ul [ i ] === 'undefined' ) {
if ( ! uLine . direction
|| uLine . direction === 'sendrecv' )
uLine . direction = 'recvonly' ;
else if ( uLine . direction === 'sendonly' )
uLine . direction = 'inactive' ;
} else {
if ( ! uLine . direction
|| uLine . direction === 'sendrecv' )
uLine . direction = 'sendrecv' ;
else if ( uLine . direction === 'recvonly' )
uLine . direction = 'sendonly' ;
}
uLine . sources = sources2ul [ i ] ;
uLine . candidates = candidates ;
uLine . iceUfrag = iceUfrag ;
uLine . icePwd = icePwd ;
uLine . fingerprint = fingerprint ;
uLine . rtp = rtp [ uLine . type ] ;
uLine . payloads = payloads [ uLine . type ] ;
uLine . rtcpFb = rtcpFb [ uLine . type ] ;
session . media . push ( uLine ) ;
if ( typeof uLine . mid === 'string' ) {
// inactive lines don't/may not have an mid.
mids . push ( uLine . mid ) ;
}
}
} else {
// SDP offer/answer (and the JSEP spec) forbids removing an m-section
// under any circumstances. If we are no longer interested in sending a
// track, we just remove the msid and ssrc attributes and set it to
// either a=recvonly (as the reofferer, we must use recvonly if the
// other side was previously sending on the m-section, but we can also
// leave the possibility open if it wasn't previously in use), or
// a=inactive.
if ( typeof cached !== 'undefined' &&
typeof cached . media !== 'undefined' &&
Array . isArray ( cached . media ) ) {
cached . media . forEach ( function ( uLine ) {
mids . push ( uLine . mid ) ;
if ( typeof mid2ul [ uLine . mid ] !== 'undefined' ) {
session . media . push ( mid2ul [ uLine . mid ] ) ;
} else {
delete uLine . msid ;
delete uLine . sources ;
delete uLine . ssrcGroups ;
if ( ! uLine . direction
|| uLine . direction === 'sendrecv' ) {
uLine . direction = 'sendonly' ;
}
if ( ! uLine . direction
|| uLine . direction === 'recvonly' ) {
uLine . direction = 'inactive' ;
}
addSetupAttr ( uLine ) ;
session . media . push ( uLine ) ;
}
} ) ;
}
// Add all the remaining (new) m-lines of the transformed SDP.
Object . keys ( mid2ul ) . forEach ( function ( mid ) {
if ( mids . indexOf ( mid ) === - 1 ) {
mids . push ( mid ) ;
if ( mid2ul [ mid ] . direction === 'recvonly' ) {
// This is a remote recvonly channel. Add its SSRC to the
// appropriate sendrecv or sendonly channel.
// TODO(gp) what if we don't have sendrecv/sendonly
// channel?
var done = false ;
session . media . some ( function ( uLine ) {
if ( ( uLine . direction === 'sendrecv' ||
uLine . direction === 'sendonly' ) &&
uLine . type === mid2ul [ mid ] . type ) {
// mid2ul[mid] shouldn't have any ssrc-groups
Object . keys ( mid2ul [ mid ] . sources ) . forEach (
function ( ssrc ) {
uLine . sources [ ssrc ] =
mid2ul [ mid ] . sources [ ssrc ] ;
} ) ;
done = true ;
return true ;
}
} ) ;
if ( ! done ) {
session . media . push ( mid2ul [ mid ] ) ;
}
} else {
session . media . push ( mid2ul [ mid ] ) ;
}
}
} ) ;
}
// After we have constructed the Plan Unified m-lines we can figure out
// where (in which m-line) to place the 'recvonly SSRCs'.
// Note: we assume here that we are the answerer in the O/A, so any offers
// which we translate come from the remote side, while answers are local
// (and so our last local description is cached as an 'answer').
[ "audio" , "video" ] . forEach ( function ( type ) {
if ( ! session || ! session . media || ! Array . isArray ( session . media ) )
return ;
var idx = null ;
if ( Object . keys ( recvonlySsrcs [ type ] ) . length > 0 ) {
idx = self . getFirstSendingIndexFromAnswer ( type ) ;
if ( idx === null ) {
// If this is the first offer we receive, we don't have a
// cached answer. Assume that we will be sending media using
// the first m-line for each media type.
for ( var i = 0 ; i < session . media . length ; i ++ ) {
if ( session . media [ i ] . type === type ) {
idx = i ;
break ;
}
}
}
}
if ( idx && session . media . length > idx ) {
var mLine = session . media [ idx ] ;
Object . keys ( recvonlySsrcs [ type ] ) . forEach ( function ( ssrc ) {
if ( mLine . sources && mLine . sources [ ssrc ] ) {
console . warn ( "Replacing an existing SSRC." ) ;
}
if ( ! mLine . sources ) {
mLine . sources = { } ;
}
mLine . sources [ ssrc ] = recvonlySsrcs [ type ] [ ssrc ] ;
} ) ;
}
} ) ;
if ( typeof session . groups !== 'undefined' ) {
// We regenerate the BUNDLE group (since we regenerated the mids)
session . groups . some ( function ( group ) {
if ( group . type === 'BUNDLE' ) {
group . mids = mids . join ( ' ' ) ;
return true ;
}
} ) ;
}
// msid semantic
session . msidSemantic = {
semantic : 'WMS' ,
token : '*'
} ;
var resStr = transform . write ( session ) ;
// Cache the transformed SDP (Unified Plan) for later re-use in this
// function.
this . cache [ desc . type ] = resStr ;
return new RTCSessionDescription ( {
type : desc . type ,
sdp : resStr
} ) ;
//#endregion
} ;
} , { "./array-equals" : 17 , "./transform" : 20 } ] , 20 : [ function ( require , module , exports ) {
/ * C o p y r i g h t @ 2 0 1 5 A t l a s s i a n P t y L t d
*
* 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 .
* /
var transform = require ( 'sdp-transform' ) ;
exports . write = function ( session , opts ) {
if ( typeof session !== 'undefined' &&
typeof session . media !== 'undefined' &&
Array . isArray ( session . media ) ) {
session . media . forEach ( function ( mLine ) {
// expand sources to ssrcs
if ( typeof mLine . sources !== 'undefined' &&
Object . keys ( mLine . sources ) . length !== 0 ) {
mLine . ssrcs = [ ] ;
Object . keys ( mLine . sources ) . forEach ( function ( ssrc ) {
var source = mLine . sources [ ssrc ] ;
Object . keys ( source ) . forEach ( function ( attribute ) {
mLine . ssrcs . push ( {
id : ssrc ,
attribute : attribute ,
value : source [ attribute ]
} ) ;
} ) ;
} ) ;
delete mLine . sources ;
}
// join ssrcs in ssrc groups
if ( typeof mLine . ssrcGroups !== 'undefined' &&
Array . isArray ( mLine . ssrcGroups ) ) {
mLine . ssrcGroups . forEach ( function ( ssrcGroup ) {
if ( typeof ssrcGroup . ssrcs !== 'undefined' &&
Array . isArray ( ssrcGroup . ssrcs ) ) {
ssrcGroup . ssrcs = ssrcGroup . ssrcs . join ( ' ' ) ;
}
} ) ;
}
} ) ;
}
// join group mids
if ( typeof session !== 'undefined' &&
typeof session . groups !== 'undefined' && Array . isArray ( session . groups ) ) {
session . groups . forEach ( function ( g ) {
if ( typeof g . mids !== 'undefined' && Array . isArray ( g . mids ) ) {
g . mids = g . mids . join ( ' ' ) ;
}
} ) ;
}
return transform . write ( session , opts ) ;
} ;
exports . parse = function ( sdp ) {
var session = transform . parse ( sdp ) ;
if ( typeof session !== 'undefined' && typeof session . media !== 'undefined' &&
Array . isArray ( session . media ) ) {
session . media . forEach ( function ( mLine ) {
// group sources attributes by ssrc
if ( typeof mLine . ssrcs !== 'undefined' && Array . isArray ( mLine . ssrcs ) ) {
mLine . sources = { } ;
mLine . ssrcs . forEach ( function ( ssrc ) {
if ( ! mLine . sources [ ssrc . id ] )
mLine . sources [ ssrc . id ] = { } ;
mLine . sources [ ssrc . id ] [ ssrc . attribute ] = ssrc . value ;
} ) ;
delete mLine . ssrcs ;
}
// split ssrcs in ssrc groups
if ( typeof mLine . ssrcGroups !== 'undefined' &&
Array . isArray ( mLine . ssrcGroups ) ) {
mLine . ssrcGroups . forEach ( function ( ssrcGroup ) {
if ( typeof ssrcGroup . ssrcs === 'string' ) {
ssrcGroup . ssrcs = ssrcGroup . ssrcs . split ( ' ' ) ;
}
} ) ;
}
} ) ;
}
// split group mids
if ( typeof session !== 'undefined' &&
typeof session . groups !== 'undefined' && Array . isArray ( session . groups ) ) {
session . groups . forEach ( function ( g ) {
if ( typeof g . mids === 'string' ) {
g . mids = g . mids . split ( ' ' ) ;
}
} ) ;
}
return session ;
} ;
} , { "sdp-transform" : 14 } ] , 21 : [ function ( require , module , exports ) {
2021-03-19 01:05:27 +08:00
/ * !
* UAParser . js v0 . 7.24
2018-01-16 04:15:58 +08:00
* Lightweight JavaScript - based User - Agent string parser
* https : //github.com/faisalman/ua-parser-js
*
2021-03-19 01:05:27 +08:00
* Copyright © 2012 - 2021 Faisal Salman < f @ faisalman . com >
* Licensed under MIT License
2018-01-16 04:15:58 +08:00
* /
( function ( window , undefined ) {
'use strict' ;
//////////////
// Constants
/////////////
2021-03-19 01:05:27 +08:00
var LIBVERSION = '0.7.24' ,
2018-01-16 04:15:58 +08:00
EMPTY = '' ,
UNKNOWN = '?' ,
FUNC _TYPE = 'function' ,
UNDEF _TYPE = 'undefined' ,
OBJ _TYPE = 'object' ,
STR _TYPE = 'string' ,
MAJOR = 'major' , // deprecated
MODEL = 'model' ,
NAME = 'name' ,
TYPE = 'type' ,
VENDOR = 'vendor' ,
VERSION = 'version' ,
ARCHITECTURE = 'architecture' ,
CONSOLE = 'console' ,
MOBILE = 'mobile' ,
TABLET = 'tablet' ,
SMARTTV = 'smarttv' ,
WEARABLE = 'wearable' ,
EMBEDDED = 'embedded' ;
///////////
// Helper
//////////
var util = {
extend : function ( regexes , extensions ) {
2021-03-19 01:05:27 +08:00
var mergedRegexes = { } ;
2018-01-16 04:15:58 +08:00
for ( var i in regexes ) {
if ( extensions [ i ] && extensions [ i ] . length % 2 === 0 ) {
2021-03-19 01:05:27 +08:00
mergedRegexes [ i ] = extensions [ i ] . concat ( regexes [ i ] ) ;
2018-01-16 04:15:58 +08:00
} else {
2021-03-19 01:05:27 +08:00
mergedRegexes [ i ] = regexes [ i ] ;
2018-01-16 04:15:58 +08:00
}
}
2021-03-19 01:05:27 +08:00
return mergedRegexes ;
2018-01-16 04:15:58 +08:00
} ,
has : function ( str1 , str2 ) {
if ( typeof str1 === "string" ) {
return str2 . toLowerCase ( ) . indexOf ( str1 . toLowerCase ( ) ) !== - 1 ;
} else {
return false ;
}
} ,
lowerize : function ( str ) {
return str . toLowerCase ( ) ;
} ,
major : function ( version ) {
return typeof ( version ) === STR _TYPE ? version . replace ( /[^\d\.]/g , '' ) . split ( "." ) [ 0 ] : undefined ;
} ,
trim : function ( str ) {
return str . replace ( /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g , '' ) ;
}
} ;
///////////////
// Map helper
//////////////
var mapper = {
rgx : function ( ua , arrays ) {
2021-03-19 01:05:27 +08:00
var i = 0 , j , k , p , q , matches , match ;
2018-01-16 04:15:58 +08:00
// loop through all regexes maps
while ( i < arrays . length && ! matches ) {
var regex = arrays [ i ] , // even sequence (0,2,4,..)
props = arrays [ i + 1 ] ; // odd sequence (1,3,5,..)
j = k = 0 ;
// try matching uastring with regexes
while ( j < regex . length && ! matches ) {
matches = regex [ j ++ ] . exec ( ua ) ;
if ( ! ! matches ) {
for ( p = 0 ; p < props . length ; p ++ ) {
match = matches [ ++ k ] ;
q = props [ p ] ;
// check if given property is actually array
if ( typeof q === OBJ _TYPE && q . length > 0 ) {
if ( q . length == 2 ) {
if ( typeof q [ 1 ] == FUNC _TYPE ) {
// assign modified match
this [ q [ 0 ] ] = q [ 1 ] . call ( this , match ) ;
} else {
// assign given value, ignore regex match
this [ q [ 0 ] ] = q [ 1 ] ;
}
} else if ( q . length == 3 ) {
// check whether function or regex
if ( typeof q [ 1 ] === FUNC _TYPE && ! ( q [ 1 ] . exec && q [ 1 ] . test ) ) {
// call function (usually string mapper)
this [ q [ 0 ] ] = match ? q [ 1 ] . call ( this , match , q [ 2 ] ) : undefined ;
} else {
// sanitize match using given regex
this [ q [ 0 ] ] = match ? match . replace ( q [ 1 ] , q [ 2 ] ) : undefined ;
}
} else if ( q . length == 4 ) {
this [ q [ 0 ] ] = match ? q [ 3 ] . call ( this , match . replace ( q [ 1 ] , q [ 2 ] ) ) : undefined ;
}
} else {
this [ q ] = match ? match : undefined ;
}
}
}
}
i += 2 ;
}
} ,
str : function ( str , map ) {
for ( var i in map ) {
// check if array
if ( typeof map [ i ] === OBJ _TYPE && map [ i ] . length > 0 ) {
for ( var j = 0 ; j < map [ i ] . length ; j ++ ) {
if ( util . has ( map [ i ] [ j ] , str ) ) {
return ( i === UNKNOWN ) ? undefined : i ;
}
}
} else if ( util . has ( map [ i ] , str ) ) {
return ( i === UNKNOWN ) ? undefined : i ;
}
}
return str ;
}
} ;
///////////////
// String map
//////////////
var maps = {
browser : {
oldsafari : {
version : {
'1.0' : '/8' ,
'1.2' : '/1' ,
'1.3' : '/3' ,
'2.0' : '/412' ,
'2.0.2' : '/416' ,
'2.0.3' : '/417' ,
'2.0.4' : '/419' ,
'?' : '/'
}
}
} ,
device : {
amazon : {
model : {
'Fire Phone' : [ 'SD' , 'KF' ]
}
} ,
sprint : {
model : {
'Evo Shift 4G' : '7373KT'
} ,
vendor : {
'HTC' : 'APA' ,
'Sprint' : 'Sprint'
}
}
} ,
os : {
windows : {
version : {
'ME' : '4.90' ,
'NT 3.11' : 'NT3.51' ,
'NT 4.0' : 'NT4.0' ,
'2000' : 'NT 5.0' ,
'XP' : [ 'NT 5.1' , 'NT 5.2' ] ,
'Vista' : 'NT 6.0' ,
'7' : 'NT 6.1' ,
'8' : 'NT 6.2' ,
'8.1' : 'NT 6.3' ,
'10' : [ 'NT 6.4' , 'NT 10.0' ] ,
'RT' : 'ARM'
}
}
}
} ;
//////////////
// Regex map
/////////////
var regexes = {
browser : [ [
// Presto based
/(opera\smini)\/([\w\.-]+)/i , // Opera Mini
2021-03-19 01:05:27 +08:00
/(opera\s[mobiletab]{3,6}).+version\/([\w\.-]+)/i , // Opera Mobi/Tablet
2018-01-16 04:15:58 +08:00
/(opera).+version\/([\w\.]+)/i , // Opera > 9.80
/(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80
] , [ NAME , VERSION ] , [
/(opios)[\/\s]+([\w\.]+)/i // Opera mini on iphone >= 8.0
] , [ [ NAME , 'Opera Mini' ] , VERSION ] , [
/\s(opr)\/([\w\.]+)/i // Opera Webkit
] , [ [ NAME , 'Opera' ] , VERSION ] , [
// Mixed
/(kindle)\/([\w\.]+)/i , // Kindle
2021-03-19 01:05:27 +08:00
/(lunascape|maxthon|netfront|jasmine|blazer|instagram)[\/\s]?([\w\.]*)/i ,
// Lunascape/Maxthon/Netfront/Jasmine/Blazer/Instagram
2018-01-16 04:15:58 +08:00
// Trident based
2021-03-19 01:05:27 +08:00
/(avant\s|iemobile|slim)(?:browser)?[\/\s]?([\w\.]*)/i ,
// Avant/IEMobile/SlimBrowser
/(bidubrowser|baidubrowser)[\/\s]?([\w\.]+)/i , // Baidu Browser
2018-01-16 04:15:58 +08:00
/(?:ms|\()(ie)\s([\w\.]+)/i , // Internet Explorer
// Webkit/KHTML based
2021-03-19 01:05:27 +08:00
/(rekonq)\/([\w\.]*)/i , // Rekonq
/(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon)\/([\w\.-]+)/i
// Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(konqueror)\/([\w\.]+)/i // Konqueror
] , [ [ NAME , 'Konqueror' ] , VERSION ] , [
/(trident).+rv[:\s]([\w\.]{1,9}).+like\sgecko/i // IE11
2018-01-16 04:15:58 +08:00
] , [ [ NAME , 'IE' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(edge|edgios|edga|edg)\/((\d+)?[\w\.]+)/i // Microsoft Edge
] , [ [ NAME , 'Edge' ] , VERSION ] , [
2018-01-16 04:15:58 +08:00
/(yabrowser)\/([\w\.]+)/i // Yandex
] , [ [ NAME , 'Yandex' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(Avast)\/([\w\.]+)/i // Avast Secure Browser
] , [ [ NAME , 'Avast Secure Browser' ] , VERSION ] , [
/(AVG)\/([\w\.]+)/i // AVG Secure Browser
] , [ [ NAME , 'AVG Secure Browser' ] , VERSION ] , [
2018-01-16 04:15:58 +08:00
/(puffin)\/([\w\.]+)/i // Puffin
] , [ [ NAME , 'Puffin' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(focus)\/([\w\.]+)/i // Firefox Focus
] , [ [ NAME , 'Firefox Focus' ] , VERSION ] , [
/(opt)\/([\w\.]+)/i // Opera Touch
] , [ [ NAME , 'Opera Touch' ] , VERSION ] , [
/((?:[\s\/])uc?\s?browser|(?:juc.+)ucweb)[\/\s]?([\w\.]+)/i // UCBrowser
2018-01-16 04:15:58 +08:00
] , [ [ NAME , 'UCBrowser' ] , VERSION ] , [
/(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
] , [ [ NAME , /_/g , ' ' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/((?:windowswechat)? qbcore)\/([\w\.]+).*(?:windowswechat)?/i // WeChat Desktop for Windows Built-in Browser
] , [ [ NAME , 'WeChat(Win) Desktop' ] , VERSION ] , [
2018-01-16 04:15:58 +08:00
/(micromessenger)\/([\w\.]+)/i // WeChat
] , [ [ NAME , 'WeChat' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(brave)\/([\w\.]+)/i // Brave browser
] , [ [ NAME , 'Brave' ] , VERSION ] , [
/(whale)\/([\w\.]+)/i // Whale browser
] , [ [ NAME , 'Whale' ] , VERSION ] , [
/(qqbrowserlite)\/([\w\.]+)/i // QQBrowserLite
] , [ NAME , VERSION ] , [
2018-01-16 04:15:58 +08:00
/(QQ)\/([\d\.]+)/i // QQ, aka ShouQ
] , [ NAME , VERSION ] , [
/m?(qqbrowser)[\/\s]?([\w\.]+)/i // QQBrowser
] , [ NAME , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(baiduboxapp)[\/\s]?([\w\.]+)/i // Baidu App
] , [ NAME , VERSION ] , [
/(2345Explorer)[\/\s]?([\w\.]+)/i // 2345 Browser
] , [ NAME , VERSION ] , [
/(MetaSr)[\/\s]?([\w\.]+)/i // SouGouBrowser
] , [ NAME ] , [
/(LBBROWSER)/i // LieBao Browser
] , [ NAME ] , [
/(weibo)__([\d\.]+)/i // Weibo
] , [ NAME , VERSION ] , [
2018-01-16 04:15:58 +08:00
/xiaomi\/miuibrowser\/([\w\.]+)/i // MIUI Browser
] , [ VERSION , [ NAME , 'MIUI Browser' ] ] , [
2021-03-19 01:05:27 +08:00
/;fbav\/([\w\.]+);/i // Facebook App for iOS & Android with version
2018-01-16 04:15:58 +08:00
] , [ VERSION , [ NAME , 'Facebook' ] ] , [
2022-05-07 21:34:05 +08:00
2021-03-19 01:05:27 +08:00
/FBAN\/FBIOS|FB_IAB\/FB4A/i // Facebook App for iOS & Android without version
] , [ [ NAME , 'Facebook' ] ] , [
/safari\s(line)\/([\w\.]+)/i , // Line App for iOS
/android.+(line)\/([\w\.]+)\/iab/i // Line App for Android
] , [ NAME , VERSION ] , [
2018-01-16 04:15:58 +08:00
/headlesschrome(?:\/([\w\.]+)|\s)/i // Chrome Headless
] , [ VERSION , [ NAME , 'Chrome Headless' ] ] , [
/\swv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
] , [ [ NAME , /(.+)/ , '$1 WebView' ] , VERSION ] , [
/((?:oculus|samsung)browser)\/([\w\.]+)/i
] , [ [ NAME , /(.+(?:g|us))(.+)/ , '$1 $2' ] , VERSION ] , [ // Oculus / Samsung Browser
/android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i // Android Browser
] , [ VERSION , [ NAME , 'Android Browser' ] ] , [
2021-03-19 01:05:27 +08:00
/(coc_coc_browser)\/([\w\.]+)/i // Coc Coc Browser
] , [ [ NAME , 'Coc Coc' ] , VERSION ] , [
/(sailfishbrowser)\/([\w\.]+)/i // Sailfish Browser
] , [ [ NAME , 'Sailfish Browser' ] , VERSION ] , [
2018-01-16 04:15:58 +08:00
/(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i
// Chrome/OmniWeb/Arora/Tizen/Nokia
] , [ NAME , VERSION ] , [
/(dolfin)\/([\w\.]+)/i // Dolphin
] , [ [ NAME , 'Dolphin' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(qihu|qhbrowser|qihoobrowser|360browser)/i // 360
] , [ [ NAME , '360 Browser' ] ] , [
2018-01-16 04:15:58 +08:00
/((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
] , [ [ NAME , 'Chrome' ] , VERSION ] , [
/(coast)\/([\w\.]+)/i // Opera Coast
] , [ [ NAME , 'Opera Coast' ] , VERSION ] , [
/fxios\/([\w\.-]+)/i // Firefox for iOS
] , [ VERSION , [ NAME , 'Firefox' ] ] , [
2021-03-19 01:05:27 +08:00
/version\/([\w\.]+)\s.*mobile\/\w+\s(safari)/i // Mobile Safari
2018-01-16 04:15:58 +08:00
] , [ VERSION , [ NAME , 'Mobile Safari' ] ] , [
2021-03-19 01:05:27 +08:00
/version\/([\w\.]+)\s.*(mobile\s?safari|safari)/i // Safari & Safari Mobile
2018-01-16 04:15:58 +08:00
] , [ VERSION , NAME ] , [
2021-03-19 01:05:27 +08:00
/webkit.+?(gsa)\/([\w\.]+)\s.*(mobile\s?safari|safari)(\/[\w\.]+)/i // Google Search Appliance on iOS
2018-01-16 04:15:58 +08:00
] , [ [ NAME , 'GSA' ] , VERSION ] , [
/webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
] , [ NAME , [ VERSION , mapper . str , maps . browser . oldsafari . version ] ] , [
/(webkit|khtml)\/([\w\.]+)/i
] , [ NAME , VERSION ] , [
// Gecko based
/(navigator|netscape)\/([\w\.-]+)/i // Netscape
] , [ [ NAME , 'Netscape' ] , VERSION ] , [
/(swiftfox)/i , // Swiftfox
/(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i ,
// IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
2021-03-19 01:05:27 +08:00
/(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([\w\.-]+)$/i ,
2018-01-16 04:15:58 +08:00
// Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
2021-03-19 01:05:27 +08:00
/(firefox)\/([\w\.]+)\s[\w\s\-]+\/[\w\.]+$/i , // Other Firefox-based
/(mozilla)\/([\w\.]+)\s.+rv\:.+gecko\/\d+/i , // Mozilla
2018-01-16 04:15:58 +08:00
// Other
/(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i ,
// Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir
/(links)\s\(([\w\.]+)/i , // Links
2021-03-19 01:05:27 +08:00
/(gobrowser)\/?([\w\.]*)/i , // GoBrowser
2018-01-16 04:15:58 +08:00
/(ice\s?browser)\/v?([\w\._]+)/i , // ICE Browser
/(mosaic)[\/\s]([\w\.]+)/i // Mosaic
] , [ NAME , VERSION ]
] ,
cpu : [ [
/(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i // AMD64
] , [ [ ARCHITECTURE , 'amd64' ] ] , [
/(ia32(?=;))/i // IA32 (quicktime)
] , [ [ ARCHITECTURE , util . lowerize ] ] , [
/((?:i[346]|x)86)[;\)]/i // IA32
] , [ [ ARCHITECTURE , 'ia32' ] ] , [
// PocketPC mistakenly identified as PowerPC
/windows\s(ce|mobile);\sppc;/i
] , [ [ ARCHITECTURE , 'arm' ] ] , [
/((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i // PowerPC
] , [ [ ARCHITECTURE , /ower/ , '' , util . lowerize ] ] , [
/(sun4\w)[;\)]/i // SPARC
] , [ [ ARCHITECTURE , 'sparc' ] ] , [
2021-03-19 01:05:27 +08:00
/((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+[;l]))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i
2018-01-16 04:15:58 +08:00
// IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
] , [ [ ARCHITECTURE , util . lowerize ] ]
] ,
device : [ [
2021-03-19 01:05:27 +08:00
/\((ipad|playbook);[\w\s\),;-]+(rim|apple)/i // iPad/PlayBook
2018-01-16 04:15:58 +08:00
] , [ MODEL , VENDOR , [ TYPE , TABLET ] ] , [
/applecoremedia\/[\w\.]+ \((ipad)/ // iPad
] , [ MODEL , [ VENDOR , 'Apple' ] , [ TYPE , TABLET ] ] , [
/(apple\s{0,1}tv)/i // Apple TV
2021-03-19 01:05:27 +08:00
] , [ [ MODEL , 'Apple TV' ] , [ VENDOR , 'Apple' ] , [ TYPE , SMARTTV ] ] , [
2018-01-16 04:15:58 +08:00
/(archos)\s(gamepad2?)/i , // Archos
/(hp).+(touchpad)/i , // HP TouchPad
/(hp).+(tablet)/i , // HP Tablet
/(kindle)\/([\w\.]+)/i , // Kindle
/\s(nook)[\w\s]+build\/(\w+)/i , // Nook
/(dell)\s(strea[kpr\s\d]*[\dko])/i // Dell Streak
] , [ VENDOR , MODEL , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/(alexa)webm/i ,
/(kf[A-z]+)(\sbuild\/|\)).+silk\//i // Kindle Fire HD
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Amazon' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/(sd|kf)[0349hijorstuw]+(\sbuild\/|\)).+silk\//i // Fire Phone
2018-01-16 04:15:58 +08:00
] , [ [ MODEL , mapper . str , maps . device . amazon . model ] , [ VENDOR , 'Amazon' ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+aft([\w])(\sbuild\/|\))/i // Fire TV
] , [ MODEL , [ VENDOR , 'Amazon' ] , [ TYPE , SMARTTV ] ] , [
2018-01-16 04:15:58 +08:00
/\((ip[honed|\s\w*]+);.+(apple)/i // iPod/iPhone
] , [ MODEL , VENDOR , [ TYPE , MOBILE ] ] , [
/\((ip[honed|\s\w*]+);/i // iPod/iPhone
] , [ MODEL , [ VENDOR , 'Apple' ] , [ TYPE , MOBILE ] ] , [
/(blackberry)[\s-]?(\w+)/i , // BlackBerry
2021-03-19 01:05:27 +08:00
/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron)[\s_-]?([\w-]*)/i ,
2018-01-16 04:15:58 +08:00
// BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Meizu/Motorola/Polytron
/(hp)\s([\w\s]+\w)/i , // HP iPAQ
/(asus)-?(\w+)/i // Asus
] , [ VENDOR , MODEL , [ TYPE , MOBILE ] ] , [
/\(bb10;\s(\w+)/i // BlackBerry 10
] , [ MODEL , [ VENDOR , 'BlackBerry' ] , [ TYPE , MOBILE ] ] , [
// Asus Tablets
2021-03-19 01:05:27 +08:00
/android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone|p00c)/i
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Asus' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/sony\stablet\s[ps]\sbuild\//i , // Sony
/(?:sony)?sgp\w+(?:\sbuild\/|\))/i
] , [ [ MODEL , 'Xperia Tablet' ] , [ VENDOR , 'Sony' ] , [ TYPE , TABLET ] ] , [
/android.+\s([c-g]\d{4}|so[-l]\w+|xq-a\w[4-7][12])(?=\sbuild\/|\).+chrome\/(?![1-6]{0,1}\d\.))/i
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Sony' ] , [ TYPE , MOBILE ] ] , [
/\s(ouya)\s/i , // Ouya
2021-03-19 01:05:27 +08:00
/(nintendo)\s([wids3utch]+)/i // Nintendo
2018-01-16 04:15:58 +08:00
] , [ VENDOR , MODEL , [ TYPE , CONSOLE ] ] , [
/android.+;\s(shield)\sbuild/i // Nvidia
] , [ MODEL , [ VENDOR , 'Nvidia' ] , [ TYPE , CONSOLE ] ] , [
2021-03-19 01:05:27 +08:00
/(playstation\s[345portablevi]+)/i // Playstation
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Sony' ] , [ TYPE , CONSOLE ] ] , [
/(sprint\s(\w+))/i // Sprint Phones
] , [ [ VENDOR , mapper . str , maps . device . sprint . vendor ] , [ MODEL , mapper . str , maps . device . sprint . model ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/(htc)[;_\s-]{1,2}([\w\s]+(?=\)|\sbuild)|\w+)/i , // HTC
/(zte)-(\w*)/i , // ZTE
/(alcatel|geeksphone|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]*)/i
// Alcatel/GeeksPhone/Nexian/Panasonic/Sony
2018-01-16 04:15:58 +08:00
] , [ VENDOR , [ MODEL , /_/g , ' ' ] , [ TYPE , MOBILE ] ] , [
/(nexus\s9)/i // HTC Nexus 9
] , [ MODEL , [ VENDOR , 'HTC' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/d\/huawei([\w\s-]+)[;\)]/i , // Huawei
/android.+\s(nexus\s6p|vog-[at]?l\d\d|ane-[at]?l[x\d]\d|eml-a?l\d\da?|lya-[at]?l\d[\dc]|clt-a?l\d\di?)/i ,
/android.+\s((?:A(?:GS2?|KA|LP|N[AE]|QM|RE|SK|TH)|B(?:A(?:C|H2)|G2|KL|LA|MH|Z[AKT])|C(?:AZ|DY|LT|OL|[MOR]R)|DUK|E(?:BG|DI|L[ES]|ML|V[AR])|FRD|G(?:LK|RA)|H(?:D[LN]|MA|LK|RY|WI)|INE|J(?:DN2|MM|NY|SN)|K(?:NT|OB|SA)|L(?:IO|LD|ON|[RY]A)|M(?:AR|ED|[HL]A|ON|RX|T7)|N(?:EO|TS|XT)|OXF|P(?:AR|CT|IC|LK|RA)|R(?:IO|VL)|S(?:C[ML]|EA|HT|PN|TF)|T(?:A[HS]|NY)|V(?:[CI]E|KY|OG|RD)|W(?:AS|LZ)|Y(?:635|AL))-[ATU][LN][01259][019])[;\)\s]/i
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Huawei' ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+(bah2?-a?[lw]\d{2})/i // Huawei MediaPad
] , [ MODEL , [ VENDOR , 'Huawei' ] , [ TYPE , TABLET ] ] , [
2018-01-16 04:15:58 +08:00
/(microsoft);\s(lumia[\s\w]+)/i // Microsoft Lumia
] , [ VENDOR , MODEL , [ TYPE , MOBILE ] ] , [
/[\s\(;](xbox(?:\sone)?)[\s\);]/i // Microsoft Xbox
] , [ MODEL , [ VENDOR , 'Microsoft' ] , [ TYPE , CONSOLE ] ] , [
/(kin\.[onetw]{3})/i // Microsoft Kin
] , [ [ MODEL , /\./g , ' ' ] , [ VENDOR , 'Microsoft' ] , [ TYPE , MOBILE ] ] , [
// Motorola
2021-03-19 01:05:27 +08:00
/\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?:?(\s4g)?)[\w\s]+build\//i ,
/\smot[\s-](\w*)/i ,
/(moto[\s\w\(\)]+(?=\sbuild|\)))/i ,
2018-01-16 04:15:58 +08:00
/(XT\d{3,4}) build\//i ,
/(nexus\s6)/i
] , [ MODEL , [ VENDOR , 'Motorola' ] , [ TYPE , MOBILE ] ] , [
/android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i
] , [ MODEL , [ VENDOR , 'Motorola' ] , [ TYPE , TABLET ] ] , [
/hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i // HbbTV devices
] , [ [ VENDOR , util . trim ] , [ MODEL , util . trim ] , [ TYPE , SMARTTV ] ] , [
/hbbtv.+maple;(\d+)/i
] , [ [ MODEL , /^/ , 'SmartTV' ] , [ VENDOR , 'Samsung' ] , [ TYPE , SMARTTV ] ] , [
/\(dtv[\);].+(aquos)/i // Sharp
] , [ MODEL , [ VENDOR , 'Sharp' ] , [ TYPE , SMARTTV ] ] , [
2021-03-19 01:05:27 +08:00
/android.+((sch-i[89]0\d|shw-m380s|SM-P605|SM-P610|SM-P587|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i ,
2018-01-16 04:15:58 +08:00
/((SM-T\w+))/i
] , [ [ VENDOR , 'Samsung' ] , MODEL , [ TYPE , TABLET ] ] , [ // Samsung
/smart-tv.+(samsung)/i
] , [ VENDOR , [ TYPE , SMARTTV ] , MODEL ] , [
/((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i ,
2021-03-19 01:05:27 +08:00
/\s(sam)(?:sung)[\s-]([\w-]+)/i ,
2018-01-16 04:15:58 +08:00
/sec-((sgh\w+))/i
] , [ [ VENDOR , 'Samsung' ] , MODEL , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/sie-(\w*)/i // Siemens
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Siemens' ] , [ TYPE , MOBILE ] ] , [
/(maemo|nokia).*(n900|lumia\s\d+)/i , // Nokia
2021-03-19 01:05:27 +08:00
/(nokia)[\s_-]?([\w\.-]*)/i
2018-01-16 04:15:58 +08:00
] , [ [ VENDOR , 'Nokia' ] , MODEL , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android[x\d\.\s;]+\s([ab][1-7]\-?[0178a]\d\d?)/i // Acer
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Acer' ] , [ TYPE , TABLET ] ] , [
/android.+([vl]k\-?\d{3})\s+build/i // LG Tablet
] , [ MODEL , [ VENDOR , 'LG' ] , [ TYPE , TABLET ] ] , [
/android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet
] , [ [ VENDOR , 'LG' ] , MODEL , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/linux;\snetcast.+smarttv/i , // LG SmartTV
/lg\snetcast\.tv-201\d/i
] , [ [ VENDOR , 'LG' ] , MODEL , [ TYPE , SMARTTV ] ] , [
2018-01-16 04:15:58 +08:00
/(nexus\s[45])/i , // LG
2021-03-19 01:05:27 +08:00
/lg[e;\s\/-]+(\w*)/i ,
2018-01-16 04:15:58 +08:00
/android.+lg(\-?[\d\w]+)\s+build/i
] , [ MODEL , [ VENDOR , 'LG' ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/(lenovo)\s?(s(?:5000|6000)(?:[\w-]+)|tab(?:[\s\w]+)|[\w-]+)/i // Lenovo tablets
] , [ VENDOR , MODEL , [ TYPE , TABLET ] ] , [
2018-01-16 04:15:58 +08:00
/android.+(ideatab[a-z0-9\-\s]+)/i // Lenovo
] , [ MODEL , [ VENDOR , 'Lenovo' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/(lenovo)[_\s-]?([\w-]+)/i
] , [ VENDOR , MODEL , [ TYPE , MOBILE ] ] , [
2018-01-16 04:15:58 +08:00
/linux;.+((jolla));/i // Jolla
] , [ VENDOR , MODEL , [ TYPE , MOBILE ] ] , [
/((pebble))app\/[\d\.]+\s/i // Pebble
] , [ VENDOR , MODEL , [ TYPE , WEARABLE ] ] , [
/android.+;\s(oppo)\s?([\w\s]+)\sbuild/i // OPPO
] , [ VENDOR , MODEL , [ TYPE , MOBILE ] ] , [
/crkey/i // Google Chromecast
2021-03-19 01:05:27 +08:00
] , [ [ MODEL , 'Chromecast' ] , [ VENDOR , 'Google' ] , [ TYPE , SMARTTV ] ] , [
2018-01-16 04:15:58 +08:00
/android.+;\s(glass)\s\d/i // Google Glass
] , [ MODEL , [ VENDOR , 'Google' ] , [ TYPE , WEARABLE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(pixel c)[\s)]/i // Google Pixel C
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Google' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(pixel( [2-9]a?)?( xl)?)[\s)]/i // Google Pixel
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Google' ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(\w+)\s+build\/hm\1/i , // Xiaomi Hongmi 'numeric' models
/android.+(hm[\s\-_]?note?[\s_]?(?:\d\w)?)\sbuild/i , // Xiaomi Hongmi
2022-05-07 21:34:05 +08:00
/android.+(redmi[\s\-_]?(?:note|k)?(?:[\s_]?[\w\s]+))(?:\sbuild|\))/i ,
2021-03-19 01:05:27 +08:00
// Xiaomi Redmi
/android.+(mi[\s\-_]?(?:a\d|one|one[\s_]plus|note lte)?[\s_]?(?:\d?\w?)[\s_]?(?:plus)?)\sbuild/i
// Xiaomi Mi
2018-01-16 04:15:58 +08:00
] , [ [ MODEL , /_/g , ' ' ] , [ VENDOR , 'Xiaomi' ] , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+(mi[\s\-_]?(?:pad)(?:[\s_]?[\w\s]+))(?:\sbuild|\))/i // Mi Pad tablets
2018-01-16 04:15:58 +08:00
] , [ [ MODEL , /_/g , ' ' ] , [ VENDOR , 'Xiaomi' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(m[1-5]\snote)\sbuild/i // Meizu
] , [ MODEL , [ VENDOR , 'Meizu' ] , [ TYPE , MOBILE ] ] , [
/(mz)-([\w-]{2,})/i
] , [ [ VENDOR , 'Meizu' ] , MODEL , [ TYPE , MOBILE ] ] , [
2018-01-16 04:15:58 +08:00
2021-03-19 01:05:27 +08:00
/android.+a000(1)\s+build/i , // OnePlus
/android.+oneplus\s(a\d{4})[\s)]/i
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'OnePlus' ] , [ TYPE , MOBILE ] ] , [
/android.+[;\/]\s*(RCT[\d\w]+)\s+build/i // RCA Tablets
] , [ MODEL , [ VENDOR , 'RCA' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/\s](Venue[\d\s]{2,7})\s+build/i // Dell Venue Tablets
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Dell' ] , [ TYPE , TABLET ] ] , [
/android.+[;\/]\s*(Q[T|M][\d\w]+)\s+build/i // Verizon Tablet
] , [ MODEL , [ VENDOR , 'Verizon' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s+(Barnes[&\s]+Noble\s+|BN[RT])(\S(?:.*\S)?)\s+build/i // Barnes & Noble Tablet
2018-01-16 04:15:58 +08:00
] , [ [ VENDOR , 'Barnes & Noble' ] , MODEL , [ TYPE , TABLET ] ] , [
/android.+[;\/]\s+(TM\d{3}.*\b)\s+build/i // Barnes & Noble Tablet
] , [ MODEL , [ VENDOR , 'NuVision' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(k88)\sbuild/i // ZTE K Series Tablet
] , [ MODEL , [ VENDOR , 'ZTE' ] , [ TYPE , TABLET ] ] , [
/android.+;\s(nx\d{3}j)\sbuild/i // ZTE Nubia
] , [ MODEL , [ VENDOR , 'ZTE' ] , [ TYPE , MOBILE ] ] , [
2018-01-16 04:15:58 +08:00
/android.+[;\/]\s*(gen\d{3})\s+build.*49h/i // Swiss GEN Mobile
] , [ MODEL , [ VENDOR , 'Swiss' ] , [ TYPE , MOBILE ] ] , [
/android.+[;\/]\s*(zur\d{3})\s+build/i // Swiss ZUR Tablet
] , [ MODEL , [ VENDOR , 'Swiss' ] , [ TYPE , TABLET ] ] , [
/android.+[;\/]\s*((Zeki)?TB.*\b)\s+build/i // Zeki Tablets
] , [ MODEL , [ VENDOR , 'Zeki' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/(android).+[;\/]\s+([YR]\d{2})\s+build/i ,
/android.+[;\/]\s+(Dragon[\-\s]+Touch\s+|DT)(\w{5})\sbuild/i // Dragon Touch Tablet
2018-01-16 04:15:58 +08:00
] , [ [ VENDOR , 'Dragon Touch' ] , MODEL , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*(NS-?\w{0,9})\sbuild/i // Insignia Tablets
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'Insignia' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*((NXA|Next)-?\w{0,9})\s+build/i // NextBook Tablets
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'NextBook' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*(Xtreme\_)?(V(1[045]|2[015]|30|40|60|7[05]|90))\s+build/i
2018-01-16 04:15:58 +08:00
] , [ [ VENDOR , 'Voice' ] , MODEL , [ TYPE , MOBILE ] ] , [ // Voice Xtreme Phones
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*(LVTEL\-)?(V1[12])\s+build/i // LvTel Phones
2018-01-16 04:15:58 +08:00
] , [ [ VENDOR , 'LvTel' ] , MODEL , [ TYPE , MOBILE ] ] , [
2021-03-19 01:05:27 +08:00
/android.+;\s(PH-1)\s/i
] , [ MODEL , [ VENDOR , 'Essential' ] , [ TYPE , MOBILE ] ] , [ // Essential PH-1
2018-01-16 04:15:58 +08:00
/android.+[;\/]\s*(V(100MD|700NA|7011|917G).*\b)\s+build/i // Envizen Tablets
] , [ MODEL , [ VENDOR , 'Envizen' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*(Le[\s\-]+Pan)[\s\-]+(\w{1,9})\s+build/i // Le Pan Tablets
2018-01-16 04:15:58 +08:00
] , [ VENDOR , MODEL , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/android.+[;\/]\s*(Trio[\s\w\-\.]+)\s+build/i // MachSpeed Tablets
2018-01-16 04:15:58 +08:00
] , [ MODEL , [ VENDOR , 'MachSpeed' ] , [ TYPE , TABLET ] ] , [
/android.+[;\/]\s*(Trinity)[\-\s]*(T\d{3})\s+build/i // Trinity Tablets
] , [ VENDOR , MODEL , [ TYPE , TABLET ] ] , [
/android.+[;\/]\s*TU_(1491)\s+build/i // Rotor Tablets
] , [ MODEL , [ VENDOR , 'Rotor' ] , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
//android.+(KS(.+))\s+build/i // Amazon Kindle Tablets
//], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
2018-01-16 04:15:58 +08:00
2021-03-19 01:05:27 +08:00
/android.+(Gigaset)[\s\-]+(Q\w{1,9})\s+build/i // Gigaset Tablets
2018-01-16 04:15:58 +08:00
] , [ VENDOR , MODEL , [ TYPE , TABLET ] ] , [
2021-03-19 01:05:27 +08:00
/[\s\/\(](android\stv|smart-?tv)[;\)\s]/i // SmartTV from Unidentified Vendors
] , [ [ TYPE , SMARTTV ] ] , [
// Android Phones from Unidentified Vendors
/android .+?; ([^;]+?)(?: build|\) applewebkit).+? mobile safari/i
] , [ MODEL , [ TYPE , MOBILE ] ] , [
// Android Tablets from Unidentified Vendors
/android .+?;\s([^;]+?)(?: build|\) applewebkit).+?(?! mobile) safari/i
] , [ MODEL , [ TYPE , TABLET ] ] , [
2018-01-16 04:15:58 +08:00
/\s(tablet|tab)[;\/]/i , // Unidentifiable Tablet
/\s(mobile)(?:[;\/]|\ssafari)/i // Unidentifiable Mobile
] , [ [ TYPE , util . lowerize ] , VENDOR , MODEL ] , [
2021-03-19 01:05:27 +08:00
/(android[\w\.\s\-]{0,9});.+build/i // Generic Android Device
] , [ MODEL , [ VENDOR , 'Generic' ] ] , [
2018-01-16 04:15:58 +08:00
2021-03-19 01:05:27 +08:00
/(phone)/i
] , [ [ TYPE , MOBILE ] ]
2018-01-16 04:15:58 +08:00
] ,
engine : [ [
/windows.+\sedge\/([\w\.]+)/i // EdgeHTML
] , [ VERSION , [ NAME , 'EdgeHTML' ] ] , [
2021-03-19 01:05:27 +08:00
/webkit\/537\.36.+chrome\/(?!27)([\w\.]+)/i // Blink
] , [ VERSION , [ NAME , 'Blink' ] ] , [
2018-01-16 04:15:58 +08:00
/(presto)\/([\w\.]+)/i , // Presto
2021-03-19 01:05:27 +08:00
/(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w\.]+)/i ,
// WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m/Goanna
2018-01-16 04:15:58 +08:00
/(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i , // KHTML/Tasman/Links
/(icab)[\/\s]([23]\.[\d\.]+)/i // iCab
] , [ NAME , VERSION ] , [
2021-03-19 01:05:27 +08:00
/rv\:([\w\.]{1,9}).+(gecko)/i // Gecko
2018-01-16 04:15:58 +08:00
] , [ VERSION , NAME ]
] ,
os : [ [
2021-03-19 01:05:27 +08:00
// Xbox, consider this before other Windows-based devices
/(xbox);\s+xbox\s([^\);]+)/i , // Microsoft Xbox (360, One, X, S, Series X, Series S)
2018-01-16 04:15:58 +08:00
// Windows based
/microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes)
] , [ NAME , VERSION ] , [
/(windows)\snt\s6\.2;\s(arm)/i , // Windows RT
2021-03-19 01:05:27 +08:00
/(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s\w]*)/i , // Windows Phone
2018-01-16 04:15:58 +08:00
/(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
] , [ NAME , [ VERSION , mapper . str , maps . os . windows . version ] ] , [
/(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
] , [ [ NAME , 'Windows' ] , [ VERSION , mapper . str , maps . os . windows . version ] ] , [
// Mobile/Embedded OS
/\((bb)(10);/i // BlackBerry 10
] , [ [ NAME , 'BlackBerry' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(blackberry)\w*\/?([\w\.]*)/i , // Blackberry
/(tizen|kaios)[\/\s]([\w\.]+)/i , // Tizen/KaiOS
/(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|sailfish|contiki)[\/\s-]?([\w\.]*)/i
// Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki/Sailfish OS
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]*)/i // Symbian
2018-01-16 04:15:58 +08:00
] , [ [ NAME , 'Symbian' ] , VERSION ] , [
/\((series40);/i // Series 40
] , [ NAME ] , [
/mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS
] , [ [ NAME , 'Firefox OS' ] , VERSION ] , [
2021-03-19 01:05:27 +08:00
// Google Chromecast
/crkey\/([\d\.]+)/i // Google Chromecast
] , [ VERSION , [ NAME , 'Chromecast' ] ] , [
2018-01-16 04:15:58 +08:00
// Console
2021-03-19 01:05:27 +08:00
/(nintendo|playstation)\s([wids345portablevuch]+)/i , // Nintendo/Playstation
2018-01-16 04:15:58 +08:00
// GNU/Linux based
2021-03-19 01:05:27 +08:00
/(mint)[\/\s\(]?(\w*)/i , // Mint
2018-01-16 04:15:58 +08:00
/(mageia|vectorlinux)[;\s]/i , // Mageia/VectorLinux
2021-03-19 01:05:27 +08:00
/(joli|[kxln]?ubuntu|debian|suse|opensuse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]*)/i ,
2018-01-16 04:15:58 +08:00
// Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
// Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
2021-03-19 01:05:27 +08:00
/(hurd|linux)\s?([\w\.]*)/i , // Hurd/Linux
/(gnu)\s?([\w\.]*)/i // GNU
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ] , [
/(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS
] , [ [ NAME , 'Chromium OS' ] , VERSION ] , [
// Solaris
2021-03-19 01:05:27 +08:00
/(sunos)\s?([\w\.\d]*)/i // Solaris
2018-01-16 04:15:58 +08:00
] , [ [ NAME , 'Solaris' ] , VERSION ] , [
// BSD based
2021-03-19 01:05:27 +08:00
/\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]*)/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ] , [
2021-03-19 01:05:27 +08:00
/(haiku)\s(\w+)/i // Haiku
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ] , [
/cfnetwork\/.+darwin/i ,
2021-03-19 01:05:27 +08:00
/ip[honead]{2,4}(?:.*os\s([\w]+)\slike\smac|;\sopera)/i // iOS
2018-01-16 04:15:58 +08:00
] , [ [ VERSION , /_/g , '.' ] , [ NAME , 'iOS' ] ] , [
2021-03-19 01:05:27 +08:00
/(mac\sos\sx)\s?([\w\s\.]*)/i ,
2018-01-16 04:15:58 +08:00
/(macintosh|mac(?=_powerpc)\s)/i // Mac OS
] , [ [ NAME , 'Mac OS' ] , [ VERSION , /_/g , '.' ] ] , [
// Other
2021-03-19 01:05:27 +08:00
/((?:open)?solaris)[\/\s-]?([\w\.]*)/i , // Solaris
/(aix)\s((\d)(?=\.|\)|\s)[\w\.])*/i , // AIX
/(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms|fuchsia)/i ,
// Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS/Fuchsia
/(unix)\s?([\w\.]*)/i // UNIX
2018-01-16 04:15:58 +08:00
] , [ NAME , VERSION ]
]
} ;
/////////////////
// Constructor
////////////////
var UAParser = function ( uastring , extensions ) {
if ( typeof uastring === 'object' ) {
extensions = uastring ;
uastring = undefined ;
}
if ( ! ( this instanceof UAParser ) ) {
return new UAParser ( uastring , extensions ) . getResult ( ) ;
}
2021-03-19 01:05:27 +08:00
var ua = uastring || ( ( typeof window !== 'undefined' && window . navigator && window . navigator . userAgent ) ? window . navigator . userAgent : EMPTY ) ;
2018-01-16 04:15:58 +08:00
var rgxmap = extensions ? util . extend ( regexes , extensions ) : regexes ;
this . getBrowser = function ( ) {
var browser = { name : undefined , version : undefined } ;
mapper . rgx . call ( browser , ua , rgxmap . browser ) ;
browser . major = util . major ( browser . version ) ; // deprecated
return browser ;
} ;
this . getCPU = function ( ) {
var cpu = { architecture : undefined } ;
mapper . rgx . call ( cpu , ua , rgxmap . cpu ) ;
return cpu ;
} ;
this . getDevice = function ( ) {
var device = { vendor : undefined , model : undefined , type : undefined } ;
mapper . rgx . call ( device , ua , rgxmap . device ) ;
return device ;
} ;
this . getEngine = function ( ) {
var engine = { name : undefined , version : undefined } ;
mapper . rgx . call ( engine , ua , rgxmap . engine ) ;
return engine ;
} ;
this . getOS = function ( ) {
var os = { name : undefined , version : undefined } ;
mapper . rgx . call ( os , ua , rgxmap . os ) ;
return os ;
} ;
this . getResult = function ( ) {
return {
ua : this . getUA ( ) ,
browser : this . getBrowser ( ) ,
engine : this . getEngine ( ) ,
os : this . getOS ( ) ,
device : this . getDevice ( ) ,
cpu : this . getCPU ( )
} ;
} ;
this . getUA = function ( ) {
return ua ;
} ;
this . setUA = function ( uastring ) {
ua = uastring ;
return this ;
} ;
return this ;
} ;
UAParser . VERSION = LIBVERSION ;
UAParser . BROWSER = {
NAME : NAME ,
MAJOR : MAJOR , // deprecated
VERSION : VERSION
} ;
UAParser . CPU = {
ARCHITECTURE : ARCHITECTURE
} ;
UAParser . DEVICE = {
MODEL : MODEL ,
VENDOR : VENDOR ,
TYPE : TYPE ,
CONSOLE : CONSOLE ,
MOBILE : MOBILE ,
SMARTTV : SMARTTV ,
TABLET : TABLET ,
WEARABLE : WEARABLE ,
EMBEDDED : EMBEDDED
} ;
UAParser . ENGINE = {
NAME : NAME ,
VERSION : VERSION
} ;
UAParser . OS = {
NAME : NAME ,
VERSION : VERSION
} ;
///////////
// Export
//////////
// check js environment
if ( typeof ( exports ) !== UNDEF _TYPE ) {
// nodejs env
if ( typeof module !== UNDEF _TYPE && module . exports ) {
exports = module . exports = UAParser ;
}
exports . UAParser = UAParser ;
} else {
// requirejs env (optional)
2021-03-19 01:05:27 +08:00
if ( typeof ( define ) === 'function' && define . amd ) {
2018-01-16 04:15:58 +08:00
define ( function ( ) {
return UAParser ;
} ) ;
2021-03-19 01:05:27 +08:00
} else if ( typeof window !== 'undefined' ) {
2018-01-16 04:15:58 +08:00
// browser env
window . UAParser = UAParser ;
}
}
// jQuery/Zepto specific (optional)
// Note:
// In AMD env the global scope should be kept clean, but jQuery is an exception.
// jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
// and we should catch that.
2021-03-19 01:05:27 +08:00
var $ = typeof window !== 'undefined' && ( window . jQuery || window . Zepto ) ;
if ( $ && ! $ . ua ) {
2018-01-16 04:15:58 +08:00
var parser = new UAParser ( ) ;
$ . ua = parser . getResult ( ) ;
$ . ua . get = function ( ) {
return parser . getUA ( ) ;
} ;
$ . ua . set = function ( uastring ) {
parser . setUA ( uastring ) ;
var result = parser . getResult ( ) ;
for ( var prop in result ) {
$ . ua [ prop ] = result [ prop ] ;
}
} ;
}
} ) ( typeof window === 'object' ? window : this ) ;
} , { } ] , 22 : [ function ( require , module , exports ) {
( function ( global ) {
var rng ;
var crypto = global . crypto || global . msCrypto ; // for IE 11
if ( crypto && crypto . getRandomValues ) {
// WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
// Moderately fast, high quality
var _rnds8 = new Uint8Array ( 16 ) ;
rng = function whatwgRNG ( ) {
crypto . getRandomValues ( _rnds8 ) ;
return _rnds8 ;
} ;
}
if ( ! rng ) {
// Math.random()-based (RNG)
//
// If all else fails, use Math.random(). It's fast, but is of unspecified
// quality.
var _rnds = new Array ( 16 ) ;
rng = function ( ) {
for ( var i = 0 , r ; i < 16 ; i ++ ) {
if ( ( i & 0x03 ) === 0 ) r = Math . random ( ) * 0x100000000 ;
_rnds [ i ] = r >>> ( ( i & 0x03 ) << 3 ) & 0xff ;
}
return _rnds ;
} ;
}
module . exports = rng ;
} ) . call ( this , typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : { } )
} , { } ] , 23 : [ function ( require , module , exports ) {
// uuid.js
//
// Copyright (c) 2010-2012 Robert Kieffer
// MIT License - http://opensource.org/licenses/mit-license.php
// Unique ID creation requires a high quality random # generator. We feature
// detect to determine the best RNG source, normalizing to a function that
// returns 128-bits of randomness, since that's what's usually required
var _rng = require ( './rng' ) ;
// Maps for number <-> hex string conversion
var _byteToHex = [ ] ;
var _hexToByte = { } ;
for ( var i = 0 ; i < 256 ; i ++ ) {
_byteToHex [ i ] = ( i + 0x100 ) . toString ( 16 ) . substr ( 1 ) ;
_hexToByte [ _byteToHex [ i ] ] = i ;
}
// **`parse()` - Parse a UUID into it's component bytes**
function parse ( s , buf , offset ) {
var i = ( buf && offset ) || 0 , ii = 0 ;
buf = buf || [ ] ;
s . toLowerCase ( ) . replace ( /[0-9a-f]{2}/g , function ( oct ) {
if ( ii < 16 ) { // Don't overflow!
buf [ i + ii ++ ] = _hexToByte [ oct ] ;
}
} ) ;
// Zero out remaining bytes if string was short
while ( ii < 16 ) {
buf [ i + ii ++ ] = 0 ;
}
return buf ;
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
function unparse ( buf , offset ) {
var i = offset || 0 , bth = _byteToHex ;
return bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] + '-' +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] + '-' +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] + '-' +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] + '-' +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] +
bth [ buf [ i ++ ] ] + bth [ buf [ i ++ ] ] ;
}
// **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
// random #'s we need to init node and clockseq
var _seedBytes = _rng ( ) ;
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
var _nodeId = [
_seedBytes [ 0 ] | 0x01 ,
_seedBytes [ 1 ] , _seedBytes [ 2 ] , _seedBytes [ 3 ] , _seedBytes [ 4 ] , _seedBytes [ 5 ]
] ;
// Per 4.2.2, randomize (14 bit) clockseq
var _clockseq = ( _seedBytes [ 6 ] << 8 | _seedBytes [ 7 ] ) & 0x3fff ;
// Previous uuid creation time
var _lastMSecs = 0 , _lastNSecs = 0 ;
// See https://github.com/broofa/node-uuid for API details
function v1 ( options , buf , offset ) {
var i = buf && offset || 0 ;
var b = buf || [ ] ;
options = options || { } ;
var clockseq = options . clockseq !== undefined ? options . clockseq : _clockseq ;
// UUID timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
// time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
var msecs = options . msecs !== undefined ? options . msecs : new Date ( ) . getTime ( ) ;
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
var nsecs = options . nsecs !== undefined ? options . nsecs : _lastNSecs + 1 ;
// Time since last uuid creation (in msecs)
var dt = ( msecs - _lastMSecs ) + ( nsecs - _lastNSecs ) / 10000 ;
// Per 4.2.1.2, Bump clockseq on clock regression
if ( dt < 0 && options . clockseq === undefined ) {
clockseq = clockseq + 1 & 0x3fff ;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ( ( dt < 0 || msecs > _lastMSecs ) && options . nsecs === undefined ) {
nsecs = 0 ;
}
// Per 4.2.1.2 Throw error if too many uuids are requested
if ( nsecs >= 10000 ) {
throw new Error ( 'uuid.v1(): Can\'t create more than 10M uuids/sec' ) ;
}
_lastMSecs = msecs ;
_lastNSecs = nsecs ;
_clockseq = clockseq ;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
msecs += 12219292800000 ;
// `time_low`
var tl = ( ( msecs & 0xfffffff ) * 10000 + nsecs ) % 0x100000000 ;
b [ i ++ ] = tl >>> 24 & 0xff ;
b [ i ++ ] = tl >>> 16 & 0xff ;
b [ i ++ ] = tl >>> 8 & 0xff ;
b [ i ++ ] = tl & 0xff ;
// `time_mid`
var tmh = ( msecs / 0x100000000 * 10000 ) & 0xfffffff ;
b [ i ++ ] = tmh >>> 8 & 0xff ;
b [ i ++ ] = tmh & 0xff ;
// `time_high_and_version`
b [ i ++ ] = tmh >>> 24 & 0xf | 0x10 ; // include version
b [ i ++ ] = tmh >>> 16 & 0xff ;
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
b [ i ++ ] = clockseq >>> 8 | 0x80 ;
// `clock_seq_low`
b [ i ++ ] = clockseq & 0xff ;
// `node`
var node = options . node || _nodeId ;
for ( var n = 0 ; n < 6 ; n ++ ) {
b [ i + n ] = node [ n ] ;
}
return buf ? buf : unparse ( b ) ;
}
// **`v4()` - Generate random UUID**
// See https://github.com/broofa/node-uuid for API details
function v4 ( options , buf , offset ) {
// Deprecated - 'format' argument, as supported in v1.2
var i = buf && offset || 0 ;
if ( typeof ( options ) == 'string' ) {
buf = options == 'binary' ? new Array ( 16 ) : null ;
options = null ;
}
options = options || { } ;
var rnds = options . random || ( options . rng || _rng ) ( ) ;
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds [ 6 ] = ( rnds [ 6 ] & 0x0f ) | 0x40 ;
rnds [ 8 ] = ( rnds [ 8 ] & 0x3f ) | 0x80 ;
// Copy bytes to buffer, if provided
if ( buf ) {
for ( var ii = 0 ; ii < 16 ; ii ++ ) {
buf [ i + ii ] = rnds [ ii ] ;
}
}
return buf || unparse ( rnds ) ;
}
// Export public API
var uuid = v4 ;
uuid . v1 = v1 ;
uuid . v4 = v4 ;
uuid . parse = parse ;
uuid . unparse = unparse ;
module . exports = uuid ;
} , { "./rng" : 22 } ] , 24 : [ function ( require , module , exports ) {
2019-01-11 05:43:30 +08:00
/ *
WildEmitter . js is a slim little event emitter by @ henrikjoreteg largely based
on @ visionmedia ' s Emitter from UI Kit .
Why ? I wanted it standalone .
I also wanted support for wildcard emitters like this :
emitter . on ( '*' , function ( eventName , other , event , payloads ) {
} ) ;
emitter . on ( 'somenamespace*' , function ( eventName , payloads ) {
} ) ;
Please note that callbacks triggered by wildcard registered events also get
the event name as the first argument .
* /
module . exports = WildEmitter ;
function WildEmitter ( ) { }
WildEmitter . mixin = function ( constructor ) {
var prototype = constructor . prototype || constructor ;
prototype . isWildEmitter = true ;
// Listen on the given `event` with `fn`. Store a group name if present.
prototype . on = function ( event , groupName , fn ) {
this . callbacks = this . callbacks || { } ;
var hasGroup = ( arguments . length === 3 ) ,
group = hasGroup ? arguments [ 1 ] : undefined ,
func = hasGroup ? arguments [ 2 ] : arguments [ 1 ] ;
func . _groupName = group ;
( this . callbacks [ event ] = this . callbacks [ event ] || [ ] ) . push ( func ) ;
return this ;
} ;
// Adds an `event` listener that will be invoked a single
// time then automatically removed.
prototype . once = function ( event , groupName , fn ) {
var self = this ,
hasGroup = ( arguments . length === 3 ) ,
group = hasGroup ? arguments [ 1 ] : undefined ,
func = hasGroup ? arguments [ 2 ] : arguments [ 1 ] ;
function on ( ) {
self . off ( event , on ) ;
func . apply ( this , arguments ) ;
}
this . on ( event , group , on ) ;
return this ;
} ;
// Unbinds an entire group
prototype . releaseGroup = function ( groupName ) {
this . callbacks = this . callbacks || { } ;
var item , i , len , handlers ;
for ( item in this . callbacks ) {
handlers = this . callbacks [ item ] ;
for ( i = 0 , len = handlers . length ; i < len ; i ++ ) {
if ( handlers [ i ] . _groupName === groupName ) {
//console.log('removing');
// remove it and shorten the array we're looping through
handlers . splice ( i , 1 ) ;
i -- ;
len -- ;
}
}
}
return this ;
} ;
// Remove the given callback for `event` or all
// registered callbacks.
prototype . off = function ( event , fn ) {
this . callbacks = this . callbacks || { } ;
var callbacks = this . callbacks [ event ] ,
i ;
if ( ! callbacks ) return this ;
// remove all handlers
if ( arguments . length === 1 ) {
delete this . callbacks [ event ] ;
return this ;
}
// remove specific handler
i = callbacks . indexOf ( fn ) ;
callbacks . splice ( i , 1 ) ;
if ( callbacks . length === 0 ) {
delete this . callbacks [ event ] ;
}
return this ;
} ;
/// Emit `event` with the given args.
// also calls any `*` handlers
prototype . emit = function ( event ) {
this . callbacks = this . callbacks || { } ;
var args = [ ] . slice . call ( arguments , 1 ) ,
callbacks = this . callbacks [ event ] ,
specialCallbacks = this . getWildcardCallbacks ( event ) ,
i ,
len ,
item ,
listeners ;
if ( callbacks ) {
listeners = callbacks . slice ( ) ;
for ( i = 0 , len = listeners . length ; i < len ; ++ i ) {
if ( ! listeners [ i ] ) {
break ;
}
listeners [ i ] . apply ( this , args ) ;
}
}
if ( specialCallbacks ) {
len = specialCallbacks . length ;
listeners = specialCallbacks . slice ( ) ;
for ( i = 0 , len = listeners . length ; i < len ; ++ i ) {
if ( ! listeners [ i ] ) {
break ;
}
listeners [ i ] . apply ( this , [ event ] . concat ( args ) ) ;
}
}
return this ;
} ;
// Helper for for finding special wildcard event handlers that match the event
prototype . getWildcardCallbacks = function ( eventName ) {
this . callbacks = this . callbacks || { } ;
var item ,
split ,
result = [ ] ;
for ( item in this . callbacks ) {
split = item . split ( '*' ) ;
if ( item === '*' || ( split . length === 2 && eventName . slice ( 0 , split [ 0 ] . length ) === split [ 0 ] ) ) {
result = result . concat ( this . callbacks [ item ] ) ;
}
}
return result ;
} ;
} ;
WildEmitter . mixin ( WildEmitter ) ;
2018-01-16 04:15:58 +08:00
} , { } ] } , { } , [ 2 ] ) ( 2 )
2018-08-30 03:12:34 +08:00
} ) ;