����JFIF��� ( %"1"%)+...383,7(-.- 404 Not Found
Sh3ll
OdayForums


Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.20
System : Linux st2.domain.com 3.10.0-1127.10.1.el7.x86_64 #1 SMP Wed Jun 3 14:28:03 UTC 2020 x86_64
User : apache ( 48)
PHP Version : 7.4.20
Disable Function : NONE
Directory :  /proc/self/root/usr/local/FlashphonerWebCallServer/client/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //proc/self/root/usr/local/FlashphonerWebCallServer/client/Flashphoner.js
function Flashphoner() {
    if (arguments.callee.instance) {
        return arguments.callee.instance;
    }
    arguments.callee.instance = this;

    this.clientVersion = "973.2747-698f2226327441982ef86ebceebb96e9d81d8e36";
    this.clientOSVersion = window.navigator.appVersion;
    this.clientBrowserVersion = window.navigator.userAgent;

    this.webRtcMediaManager = undefined;
    this.webRtcCallSessionId = undefined;
    this.flashMediaManager = undefined;
    this.swfLoaded = undefined;
    this.wsPlayerMediaManager = undefined;
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    try {
        this.audioContext = new AudioContext();
    } catch(e) {
        console.warn("Failed to create audio context");
    }
    this.connection = null;
    this.configuration = new Configuration();
    this.calls = new DataMap();
    this.publishStreams = new DataMap();
    this.playStreams = new DataMap();
    this.messages = {};
    this.isOpened = false;
    this.listeners = {};
    this.roomListeners = {};
    this.version = undefined;
    this.mediaProviders = new DataMap();
    this.intervalId = -1;
    this.firefoxCodecReplaicer = {"pcma": "PCMA", "pcmu": "PCMU", "g722": "G722", "OPUS": "opus", "SHA-256": "sha-256"};
    this.firefoxScreenSharingExtensionInstalled = false;
}

Flashphoner.getInstance = function () {
    return new Flashphoner();
};

Flashphoner.prototype = {

    isChrome: function(){
        return (navigator.userAgent.indexOf("Chrome") > -1) && (navigator.userAgent.indexOf("Edge") == -1);
    },

    isFF: function(){
        return (navigator.userAgent.indexOf("Mozilla") > -1) && (navigator.userAgent.indexOf("Firefox") > -1) && (navigator.userAgent.indexOf("Edge") == -1);
    },

    initFlash: function (elementId, pathToSWF) {

        var me = this;

        if ( (me.isChrome() || me.isFF()) && !me.configuration.forceFlashForWebRTCBrowser ) {
            //Don't init Flash player for Chrome browser because it has some bugs in version 46 (Flash no longer detects webcam in Chrome)
            //Once Flash is not loaded, WebRTC will be used everywhere in Chrome until the Flash Player bug is not resolved
            //https://productforums.google.com/forum/#!topic/chrome/QjT1GR2IYzM;context-place=forum/chrome
            trace("Flash won't be initialized for Chrome");
            return;
        }

        if (typeof swfobject != 'undefined') {
            var params = {};
            params.menu = "true";
            params.swliveconnect = "true";
            params.allowfullscreen = "true";
            params.allowscriptaccess = "always";
            params.bgcolor = (Object.keys(me.configuration.swfParams).length === 0) ? "000000" : me.configuration.swfParams.bgcolor;
            //in case of Safari wmode should be "window"
            if ((navigator.userAgent.indexOf("Safari") > -1) && !(navigator.userAgent.indexOf("Chrome") > -1)) {
                params.wmode = (Object.keys(me.configuration.swfParams).length === 0) ? "window" : me.configuration.swfParams.wmode;
                //workaround for safari browser, FPNR-403
                swfobject.switchOffAutoHideShow();
            } else if ((navigator.userAgent.indexOf("Mozilla") > -1) && (navigator.userAgent.indexOf("Firefox") > -1)) {
                params.wmode = (Object.keys(me.configuration.swfParams).length === 0) ? "window" : me.configuration.swfParams.wmode;
            } else {
                params.wmode = (Object.keys(me.configuration.swfParams).length === 0) ? "transparent" : me.configuration.swfParams.wmode;
            }
            var attributes = {};
            var flashvars = {};
            if (swfobject.hasFlashPlayerVersion("11.2")) {
                swfobject.embedSWF(pathToSWF, elementId, "100%", "100%", "11.2.202", "expressInstall.swf", flashvars, params, attributes, function (e) {
                    me.flashMediaManager = e.ref;
                    me.swfLoaded = true;
                    me.mediaProviders.add(MediaProvider.Flash, me.flashMediaManager);
                });
            } else {
                trace("Problem: Flash not found")
            }
        } else {
            trace("Warning: swfobject.js does not include and flash does not load");
        }
    },

    initFlashMediaManager: function () {
        if (isFlashphonerAPILoaded && this.userData) {
            this.flashMediaManager.connect(this.configuration.urlFlashServer, this.userData, this.configuration);
        }
    },

    initWSPlayerMediaManager: function () {
        if (this.userData && this.wsPlayerMediaManager) {
            var config = {};
            config.token = this.userData.authToken;
            config.urlWsServer = this.connection.urlServer;
            config.receiverPath = this.configuration.wsPlayerReceiverPath;
            config.decoderPath = this.configuration.wsPlayerDecoderPath;
            config.videoWidth = this.configuration.videoWidth;
            config.videoHeight = this.configuration.videoHeight;
            config.startWithVideoOnly = this.configuration.wsPlayerStartWithVideoOnly;
            config.keepLastFrame = this.configuration.wsPlayerKeepLastFrame;
            this.wsPlayerMediaManager.initLogger(0);
            this.wsPlayerMediaManager.init(config, this.audioContext);
        }
    },

    checkMediaDevices: function() {
        return !(navigator.mediaDevices === undefined || navigator.mediaDevices.getUserMedia === undefined);
    },

    initWebRTC: function () {
        var me = this;
        if (webrtcDetectedBrowser) {
            me.webRtcMediaManager = new WebRtcMediaManager();
            me.mediaProviders.add(MediaProvider.WebRTC, me.webRtcMediaManager);

            var MediaStream = window.MediaStream;

            if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
                MediaStream = webkitMediaStream;
            }

            /*global MediaStream:true */
            if (typeof MediaStream !== 'undefined' && !('stop' in MediaStream.prototype)) {
                MediaStream.prototype.stop = function () {
                    this.getAudioTracks().forEach(function (track) {
                        track.stop();
                    });

                    this.getVideoTracks().forEach(function (track) {
                        track.stop();
                    });
                };
            }

            this.webRtcMediaManager.onLocalScreenMediaStreamEnded = function (mediaSessionId) {
                var streams = me.publishStreams.array();
                streams.some(function (stream) {
                    if (stream.mediaSessionId == mediaSessionId) {
                        stream.status = StreamStatus.LocalStreamStopped;
                        me.invokeListener(WCSEvent.StreamStatusEvent, [
                            stream
                        ]);

                        //stop stream
                        me.unPublishStream(stream);
                        return true;
                    }
                });
            }
        }
    },

    addListener: function (event, listener, thisArg) {
        this.listeners[event] = {func: listener, thisArg: thisArg};
    },

    invokeListener: function (event, argsArray) {
        var listener = this.listeners[event];
        if (listener) {
            listener.func.apply(listener.thisArg ? listener.thisArg : window, argsArray);
        }
    },

    addOrUpdateCall: function (call) {
        var me = this;
        if (me.calls.get(call.callId)) {
            me.calls.update(call.callId, call);
        } else {
            me.calls.add(call.callId, call);

            if (!call.mediaProvider) {
                call.mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
            }

            if ((!this.webRtcCallSessionId) && MediaProvider.WebRTC == call.mediaProvider) {
                this.webRtcCallSessionId = call.callId;
                me.webRtcMediaManager.newConnection(call.callId, new WebRtcMediaConnection(me.webRtcMediaManager, me.configuration.stunServer, me.configuration.useDTLS, me.configuration.remoteMediaElementId, call.callId));
            }

            if (call.incoming || call.parentCallId !== undefined) {
                me.invokeListener(WCSEvent.OnCallEvent, [
                    call
                ]);
            }
        }
    },

    init: function (configuration) {
        var me = this;
        if (!configuration) {
            configuration = new Configuration();
        }
        if (!configuration.remoteMediaElementId) {
            configuration.remoteMediaElementId = 'remoteMediaElement';
            var _body = document.getElementsByTagName('body') [0];
            var remoteMediaElement = document.createElement('audio');
            remoteMediaElement.id = configuration.remoteMediaElementId;
            _body.appendChild(remoteMediaElement);
        }

        if (!configuration.pathToSWF) {
            configuration.pathToSWF = '../../../dependencies/flash/MediaManager.swf';
        }

        if (!configuration.elementIdForSWF && typeof swfobject != 'undefined') {
            configuration.elementIdForSWF = 'flashVideoDiv';
            var _body = document.getElementsByTagName('body') [0];
            var flashVideoDiv = document.createElement('div');
            flashVideoDiv.style.width = '322px';
            flashVideoDiv.style.height = '176px';
            flashVideoDiv.innerHTML = '<div id="' + configuration.elementIdForSWF + '"></div>';
            _body.appendChild(flashVideoDiv);

        }


        me.configuration = configuration;
        me.initWebRTC();
        if (me.configuration.elementIdForSWF && me.configuration.pathToSWF) {
            me.initFlash(me.configuration.elementIdForSWF, me.configuration.pathToSWF);
        }
        if (me.configuration.wsPlayerCanvas) {
            me.wsPlayerMediaManager = new WSPlayer(me.configuration.wsPlayerCanvas, me);
            me.mediaProviders.add(MediaProvider.WSPlayer, me.wsPlayerMediaManager);
        }

        if (me.configuration.localMediaElementId) {
            try {
                getElement(me.configuration.localMediaElementId).volume = 0;
            }catch(err) {
                console.info("This browser may not support video.volume: "+err);
            }
        }

        this.callbacks = {
            ping: function () {
                me.webSocket.send("pong");
            },

            getUserData: function (userData) {
                me.userData = userData;
                if (me.flashMediaManager) {
                    me.initFlashMediaManager();
                }
                if (me.wsPlayerMediaManager) {
                    me.initWSPlayerMediaManager();
                }
                for (var prop in userData) {
                    me.connection[prop] = me.userData[prop];
                }
                me.connection.status = ConnectionStatus.Established;
                me.invokeListener(WCSEvent.ConnectionStatusEvent, [
                    me.connection
                ]);
            },

            getVersion: function (version) {
                me.version = version;
            },

            registered: function (event) {
                me.invokeListener(WCSEvent.RegistrationStatusEvent, [
                    event
                ]);
            },

            notifyIncomingCall: function (call) {
                trace("notifyIncomingCall call.callId:" + call.callId);
                me.addOrUpdateCall(call);
            },

            notifyTransferEvent: function (call) {
                trace("notifyTransferEvent " + call.status);
                if (call.status == "PENDING") {
                    me.invokeListener(WCSEvent.OnTransferEvent, [
                        call
                    ]);
                } else {
                    me.invokeListener(WCSEvent.TransferStatusEvent, [
                        call
                    ]);
                }
            },

            notifyTryingResponse: function (call) {
                trace("notifyTryingResponse call.callId:" + call.callId);
                me.addOrUpdateCall(call);
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            ring: function (call) {
                trace("ring call.status: " + call.status + " call.callId: " + call.callId);
                me.addOrUpdateCall(call);
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            sessionProgress: function (call) {
                trace("sessionProgress call.state: " + call.state + " call.callId: " + call.callId);
                me.addOrUpdateCall(call);
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            setRemoteSDP: function (id, sdp, isInitiator) {
                //if (sdp.search("a=recvonly") != -1) {
                //    sdp = me.handleVideoSSRC(sdp);
                //}
                console.log("setRemoteSDP: " + sdp);
                if (me.webRtcMediaManager) {
                    if (navigator.mozGetUserMedia) {
                        for (var c in me.firefoxCodecReplaicer) {
                            sdp = sdp.split(c).join(me.firefoxCodecReplaicer[c]);
                        }
                    }

                    if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                        sdp = me.stripCodecsSDP(sdp, false);
                        console.log("Apply strip codecs");
                    }
                    me.webRtcMediaManager.setRemoteSDP(id, sdp, isInitiator);
                }
            },

            notifyAudioCodec: function (id, codec) {
                var call = me.calls.get(id);
                if (me.flashMediaManager && call && MediaProvider.Flash == call.mediaProvider) {
                    me.flashMediaManager.setAudioCodec(id, codec);
                }
            },

            binaryData: function (data) {
                me.invokeListener(WCSEvent.OnBinaryEvent, [
                    data
                ]);
            },

            base64BinaryData: function (data) {
                var result = {};
                var raw = window.atob(data);
                var rawLength = raw.length;
                var array = new Uint8Array(new ArrayBuffer(rawLength));

                for (i = 0; i < rawLength; i++) {
                    array[i] = raw.charCodeAt(i);
                }
                result.data = array;
                console.log("received data length " + result.data.length);
                me.invokeListener(WCSEvent.OnBinaryEvent, [
                    result
                ]);
            },

            notifyVideoFormat: function (videoFormat) {
                me.invokeListener(WCSEvent.OnVideoFormatEvent, [videoFormat]);
            },

            talk: function (call) {
                me.addOrUpdateCall(call);
                if (!call.isMsrp) {
                    me.mediaProviders.get(call.mediaProvider).talk(call.callId, call.hasVideo);
                }
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            hold: function (call) {
                me.addOrUpdateCall(call);
                me.mediaProviders.get(call.mediaProvider).hold(call.callId);
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            callbackHold: function (callId, isHold) {
                trace("callbackHold " + isHold);
            },

            finish: function (call) {
                me.finish(call);
            },

            busy: function (call) {
                me.addOrUpdateCall(call);
                me.invokeListener(WCSEvent.CallStatusEvent, [
                    call
                ]);
            },

            fail: function (event) {
                if (event.hasOwnProperty("apiMethod")) {
                    var actualEvent = WCSEvent[event.apiMethod];
                    //Finish Call before raising of FAILED event to close resources properly such as peer connection
                    if (event.apiMethod == "CallStatusEvent") {
                        var call = me.calls.get(event.id);
                        if (call) {
                            call.status = CallStatus.FINISH;
                            me.finish(call);
                        }
                    }
                    delete event.apiMethod;
                    me.invokeListener(actualEvent, [
                        event
                    ]);
                } else {
                    me.invokeListener(WCSEvent.ErrorStatusEvent, [
                        event
                    ]);
                }
            },

            notifyBugReport: function (filename) {
                me.invokeListener(WCSEvent.BugReportStatusEvent, [
                    {filename: filename}
                ]);
            },

            notifyMessage: function (message, notificationResult) {
                var sentMessage = me.messages[message.id];
                if (sentMessage != null) {
                    sentMessage.status = message.status;
                }
                if (message.status == MessageStatus.RECEIVED) {
                    //here we will choose what to display on multiple contacts in "from".
                    if (message.from.indexOf(",") != -1) {
                        var fromList = message.from.split(",");
                        message.from = fromList[0];
                    }
                    notificationResult.status = "OK";
                    me.notificationResult(notificationResult);
                    me.invokeListener(WCSEvent.OnMessageEvent, [
                        message
                    ]);
                } else {
                    if (message.status == MessageStatus.ACCEPTED) {
                        if (!sentMessage.isImdnRequired) {
                            me.removeSentMessage(sentMessage);
                        }
                    } else if (message.status == MessageStatus.FAILED) {
                        me.removeSentMessage(sentMessage);
                    } else if (message.status == MessageStatus.IMDN_NOTIFICATION_SENT && sentMessage == null) {
                        me.messages[message.id] = message;
                    } else if (message.status == MessageStatus.IMDN_DELIVERED) {
                        me.removeSentMessage(sentMessage);
                        notificationResult.status = "OK";
                        me.notificationResult(notificationResult);
                    } else if (message.status == MessageStatus.IMDN_FAILED || message.status == MessageStatus.IMDN_FORBIDDEN || message.status == MessageStatus.IMDN_ERROR) {
                        me.removeSentMessage(sentMessage);
                        notificationResult.status = "OK";
                        me.notificationResult(notificationResult);
                    }
                    me.invokeListener(WCSEvent.MessageStatusEvent, [
                        message
                    ]);
                }
            },

            notifyRecordComplete: function (recordReport) {
                me.invokeListener(WCSEvent.RecordingStatusEvent, [
                    recordReport
                ]);
            },

            notifySubscription: function (subscription, sipObject) {
                me.invokeListener(WCSEvent.SubscriptionStatusEvent, [
                    subscription
                ]);
            },

            notifyXcapResponse: function (xcapResponse) {
                me.invokeListener(WCSEvent.XcapStatusEvent, [
                    xcapResponse
                ]);
            },

            notifyStreamInfoEvent: function (streamInfo) {
                me.invokeListener(WCSEvent.StreamInfoEvent, [
                    streamInfo
                ]);
            },

            notifyStreamStatusEvent: function (stream) {
                //clean resources if status is failed
                if (stream.status == StreamStatus.Failed) {
                    var removedStream;
                    if (stream.published) {
                        removedStream = me.publishStreams.remove(stream.mediaSessionId);
                    } else {
                        removedStream = me.playStreams.remove(stream.mediaSessionId);
                    }
                    if (removedStream) {
                        me.releaseMediaManagerStream(removedStream);
                    }
                } else {
                    if (stream.mediaProvider == MediaProvider.Flash) {
                        if (stream.status == StreamStatus.Publishing) {
                            me.flashMediaManager.publishStream(stream.mediaSessionId, true, stream.hasVideo, (stream.bitrate)?stream.bitrate:0, (stream.quality)?stream.quality:0);
                        }
                        if (stream.status == StreamStatus.Playing) {
                            me.flashMediaManager.playStream(stream.mediaSessionId);
                        }
                    }
                    if (stream.published) {
                        me.publishStreams.update(stream.mediaSessionId, stream);
                    } else {
                        me.playStreams.update(stream.mediaSessionId, stream);
                    }
                }
                me.invokeListener(WCSEvent.StreamStatusEvent, [
                    stream
                ]);
            },

            OnDataEvent: function (data) {
                me.invokeListener(WCSEvent.OnDataEvent, [
                    data
                ]);
                me.webSocket.send("DataStatusEvent", {status: "ACCEPTED", operationId: data.operationId});
            },

            DataStatusEvent: function (status) {
                me.invokeListener(WCSEvent.DataStatusEvent, [
                    status
                ]);
            },

            notifyRoomStatusEvent: function (roomStatusEventListener) {
                me.invokeRoomStatusEventListener(roomStatusEventListener.room, [
                    roomStatusEventListener
                ]);
            }
        };
    },

    removeSentMessage: function (sentMessage) {
        var me = this;
        setTimeout(function () {
            me.messages[sentMessage.id] = null;
        }, 5000);
    },


    connect: function (connection) {
        var me = this;
        me.connection = connection;
        if (me.connection.sipRegisterRequired == undefined) {
            me.connection.sipRegisterRequired = me.configuration.sipRegisterRequired;
        }
        me.connection.sipContactParams = me.connection.sipContactParams | me.configuration.sipContactParams;
        for (var item in me.connection) {
            if (me.connection[item] != null && me.connection[item] != undefined && !Array.isArray(me.connection[item])) {
                me.connection[item] = $.trim(me.connection[item]);
            }
        }

        if (!me.connection.mediaProviders || me.connection.mediaProviders.length == 0) {
            me.connection.mediaProviders = Object.keys(me.mediaProviders.getData());
        }
        me.connection.urlServer = me.connection.urlServer || me.configuration.urlWsServer;
        me.connection.width = me.connection.width || me.configuration.videoWidth;
        me.connection.height = me.connection.height || me.configuration.videoHeight;
        me.connection.clientVersion = me.clientVersion;
        me.connection.clientOSVersion = me.clientOSVersion;
        me.connection.clientBrowserVersion = me.clientBrowserVersion;
        //workaround for old Safari (5.X)
        if ((navigator.userAgent.indexOf("Safari") > -1) && !(navigator.userAgent.indexOf("Chrome") > -1)) {
            me.connection.urlServer = me.connection.urlServer.slice(-1) == "/" ? me.connection.urlServer + "websocket" : me.connection.urlServer + "/websocket";
        }

        var getLocation = function (href) {
            var l = document.createElement("a");
            l.href = href;
            return l;
        };
        var l = getLocation(me.connection.urlServer);

        if (!me.configuration.urlFlashServer) {
            me.configuration.urlFlashServer = "rtmfp://" + l.hostname + ":1935";
        }

        me.webSocket = $.websocket(me.connection.urlServer, {
            open: function () {
                me.isOpened = true;
                me.webSocket.send("connection", me.connection);
            },
            close: function (event) {
                me.isOpened = false;
                if (event.originalEvent.wasClean || event.originalEvent.code == 1000) {
                    me.connection.status = ConnectionStatus.Disconnected;
                } else {
                    me.connection.status = ConnectionStatus.Failed;
                }
                me.invokeListener(WCSEvent.ConnectionStatusEvent, [
                    me.connection, event.originalEvent
                ]);
                if (me.webRtcMediaManager) {
                    me.webRtcMediaManager.disconnect();
                }
                if (me.flashMediaManager) {
                    me.flashMediaManager.disconnect();
                }
                if (me.wsPlayerMediaManager) {
                    me.wsPlayerMediaManager.stop();
                }
                me.webRtcCallSessionId = undefined;
                me.calls = new DataMap();
                me.publishStreams = new DataMap();
                me.playStreams = new DataMap();
            },
            error: function () {
                me.connection.status = ConnectionStatus.Failed;
                me.invokeListener(WCSEvent.ConnectionStatusEvent, [
                    me.connection
                ]);
            },
            context: me,
            events: me.callbacks
        });
        return 0;
    },

    invokeProblem: function (status) {
        this.invokeListener(WCSEvent.ErrorStatusEvent, [
            status
        ]);
    },

    disconnect: function () {
        trace("WebSocketManager - disconnect");
        this.webSocket.close();
    },

    subscribe: function (subscribeObject) {
        this.webSocket.send("subscribe", subscribeObject);
    },

    sendXcapRequest: function (xcapObject) {
        this.webSocket.send("sendXcapRequest", xcapObject);
    },

    call: function (call) {
        var me = this;

        call.callId = createUUID();
        call.incoming = false;
        if (!call.isMsrp) {
            call.isMsrp = false;
        }
        if (call.hasAudio == undefined) {
            call.hasAudio = true;
        }
        if (!call.hasVideo) {
            call.hasVideo = false;
        }

        if (!call.receiveVideo) {
            call.receiveVideo = false;
        }

        me.addOrUpdateCall(call);

        var internalCall = function () {
            if (MediaProvider.WebRTC == call.mediaProvider) {
                me.webRtcMediaManager.createOffer(call.callId, function (sdp) {
                    //here we will strip codecs from SDP if requested
                    if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                        sdp = me.stripCodecsSDP(sdp, true);
                        console.log("New SDP: " + sdp);
                    }
                    sdp = me.removeCandidatesFromSDP(sdp);
                    call.sdp = sdp;
                    me.webSocket.send("call", call);
                }, call.hasAudio, call.hasVideo, call.receiveVideo);
            } else if (MediaProvider.Flash == call.mediaProvider) {
                me.webSocket.send("call", call);
            }
        };
        if (call.hasAudio) {
            me.checkAndGetAccess(call.mediaProvider, call.hasVideo, internalCall, []);
        } else {
            internalCall();
        }
        return call;
    },

    msrpCall: function (callRequest) {
        var me = this;
        callRequest.callId = createUUID();
        me.webSocket.send("call", callRequest);
        return callRequest;
    },

    answer: function (call) {
        var me = this;
        me.checkAndGetAccess(call.mediaProvider, call.hasVideo, function () {
            if (MediaProvider.WebRTC == call.mediaProvider) {
                /**
                 * If we receive INVITE without SDP, we should send answer with SDP based on webRtcMediaManager.createOffer because we do not have remoteSdp here
                 */
                if (me.webRtcMediaManager.receivedEmptyRemoteSDP(call.callId)) {
                    me.webRtcMediaManager.createOffer(call.callId, function (sdp) {
                        //here we will strip codecs from SDP if requested
                        if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                            sdp = me.stripCodecsSDP(sdp, true);
                            console.log("New SDP: " + sdp);
                        }
                        call.sdp = me.removeCandidatesFromSDP(sdp);
                        me.webSocket.send("answer", call);
                    }, true, call.hasVideo);
                } else {
                    /**
                     * If we receive a normal INVITE with SDP we should create answering SDP using normal createAnswer method because we already have remoteSdp here.
                     */
                    me.webRtcMediaManager.createAnswer(call.callId, function (sdp) {
                        call.sdp = sdp;
                        me.webSocket.send("answer", call);
                    }, call.hasVideo);
                }
            } else if (MediaProvider.Flash == call.mediaProvider) {
                me.webSocket.send("answer", call);
            }
        }, []);
    },

    changeVideoState: function (call, enable) {
        var me = this;
        if (MediaProvider.Flash == call.mediaProvider) {
            if (!call.hasVideo) {
                me.webSocket.send("updateCallToVideo", call.callId);
                call.hasVideo = true;
            }
            me.flashMediaManager.changeVideoState(call.callId, enable);
        } else {
            //todo uncomment after firefox implement reoffer
            //this.webRtcMediaManager.createOffer(call.callId, function (sdp) {
            //    me.webSocket.send("changeVideoState", {callId: call.callId, sdp: sdp});
            //}, true, call.hasVideo);
        }
        return 0;
    },

    hangup: function (call) {
        if (call) {
            this.webSocket.send("hangup", {callId: call.callId});
        }
    },

    finish: function (call) {
        this.calls.remove(call.callId);
        if (this.calls.getSize() == 0 && MediaProvider.WebRTC == call.mediaProvider) {
            var sessionId = this.webRtcCallSessionId;
            this.webRtcCallSessionId = undefined;
            this.mediaProviders.get(call.mediaProvider).close(sessionId);
        }
        if (MediaProvider.Flash == call.mediaProvider) {
            this.mediaProviders.get(call.mediaProvider).close(call.callId);
        }
        this.invokeListener(WCSEvent.CallStatusEvent, [
            call
        ]);
    },

    hold: function (call) {
        this.webSocket.send("hold", {callId: call.callId});
    },

    holdForTransfer: function (call) {
        this.webSocket.send("hold", {callId: call.callId, holdForTransfer: true});
    },

    unhold: function (call) {
        this.webSocket.send("unhold", {callId: call.callId});
    },

    transfer: function (transferObj) {
        this.webSocket.send("transfer", transferObj);
    },

    sendDTMF: function (dtmfObj) {
        if (!dtmfObj.type) {
            dtmfObj.type = DtmfType.rfc2833;
        }
        this.webSocket.send("sendDtmf", dtmfObj);
    },

    getCallStatistics: function (call, callbackFn) {
        if (MediaProvider.Flash == call.mediaProvider) {
            this.getStreamStatistics(call.callId, call.mediaProvider, callbackFn)
        } else {
            this.getStreamStatistics(this.webRtcCallSessionId, call.mediaProvider, callbackFn);
        }
    },

    getStreamStatistics: function (mediaSessionId, mediaProvider, callbackFn) {
        var me = this;
        if (MediaProvider.Flash == mediaProvider) {
            var statistics = this.flashMediaManager.getStatistics(mediaSessionId);
            var param;
            for (param in statistics.incomingStreams.info) {
                if (param.indexOf("audio") > -1) {
                    statistics.incomingStreams.audio[param] = statistics.incomingStreams.info[param];
                }
                if (param.indexOf("video") > -1) {
                    statistics.incomingStreams.video[param] = statistics.incomingStreams.info[param];
                }
            }
            delete statistics.incomingStreams.info;
            for (param in statistics.outgoingStreams.info) {
                if (param.indexOf("audio") > -1) {
                    statistics.outgoingStreams.audio[param] = statistics.outgoingStreams.info[param];
                }
                if (param.indexOf("video") > -1) {
                    statistics.outgoingStreams.video[param] = statistics.outgoingStreams.info[param];
                }
            }
            delete statistics.outgoingStreams.info;

            statistics.type = "flash";
            callbackFn(statistics);
        } else {
            this.webRtcMediaManager.getStatistics(mediaSessionId, callbackFn);
        }
    },

    getWSPlayerStatistics: function (type) {
        return this.wsPlayerMediaManager.getStreamStatistics(type);
    },

    setUseProxy: function (useProxy) {
        if (this.isOpened) {
            this.webSocket.send("setUseProxy", useProxy);
        }
    },

    pushLogs: function (logsObject) {
        if (this.isOpened) {
            this.webSocket.send("pushLogs", logsObject);
            return true;
        } else {
            return false;
        }
    },

    submitBugReport: function (reportObject) {
        if (this.isOpened) {
            this.webSocket.send("submitBugReport", reportObject);
            return true;
        } else {
            return false;
        }
    },

    setLTState: function (state) {
        trace("setLTState: " + state);
        this.webSocket.send("setLTState", {state: state});
    },

    getAccess: function (mediaProvider, hasVideo) {
        var me = this;
        setTimeout(function () {
            if (hasVideo) {
                if (!me.mediaProviders.get(mediaProvider).getAccessToAudioAndVideo()) {
                    me.invokeProblem({
                        status: WCSError.MIC_CAM_ACCESS_PROBLEM,
                        info: "Failed to get access to microphone or not found"
                    });
                }
            } else {
                if (!me.mediaProviders.get(mediaProvider).getAccessToAudio()) {
                    me.invokeProblem({
                        status: WCSError.MIC_ACCESS_PROBLEM,
                        info: "Failed to get access to microphone and camera or not found"
                    });
                }
            }
        }, 50);

    },

    hasAccess: function (mediaProvider, hasVideo) {
        var mp = this.mediaProviders.get(mediaProvider);
        if (hasVideo) {
            return mp.hasAccessToAudioAndVideo && mp.hasAccessToAudioAndVideo();
        } else {
            return mp.hasAccessToAudio && mp.hasAccessToAudio();
        }
    },

    releaseCameraAndMicrophone: function (mediaProvider) {
        this.mediaProviders.get(mediaProvider).releaseCameraAndMicrophone();
    },

    getVolume: function (call) {
        if (MediaProvider.Flash == call.mediaProvider) {
            this.mediaProviders.get(call.mediaProvider).getVolume(call.callId, value);
        } else {
            this.mediaProviders.get(call.mediaProvider).getVolume(this.webRtcCallSessionId, value);
        }
    },

    getVolumeOnStreaming: function(provider) {
        if(provider == MediaProvider.WebRTC) {
            return getElement(this.configuration.remoteMediaElementId).volume;
        } else {
            return this.mediaProviders.get(provider).getVolume();
        }
    },

    setVolume: function (call, value) {
        if (MediaProvider.Flash == call.mediaProvider) {
            this.mediaProviders.get(call.mediaProvider).setVolume(call.callId, value);
        } else {
            this.mediaProviders.get(call.mediaProvider).setVolume(this.webRtcCallSessionId, value);
        }
    },

    setVolumeOnStreaming: function (provider, value) {
        if (provider == MediaProvider.WSPlayer) {
            this.mediaProviders.get(provider).setVolume(value/100);
        } else if (provider == MediaProvider.Flash) {
            this.mediaProviders.get(provider).setVolume(0, value);
        } else {
           getElement(this.configuration.remoteMediaElementId).volume = value/100;
        }
    },

    muteVideo: function (mediaProvider) {
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        this.mediaProviders.get(mediaProvider).muteVideo();

    },

    unmuteVideo: function (mediaProvider) {
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        this.mediaProviders.get(mediaProvider).unmuteVideo();
    },

    isVideoMuted: function (mediaProvider) {
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        return this.mediaProviders.get(mediaProvider).isVideoMuted();

    },

    mute: function (mediaProvider) {
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        this.mediaProviders.get(mediaProvider).mute();
    },

    unmute: function (mediaProvider) {
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        this.mediaProviders.get(mediaProvider).unmute();
    },

    setMicrophoneGain: function(volume,mediaProvider) {
        if (MediaProvider.WSPlayer == mediaProvider) {
            console.warn("Flash or WebRTC media provider supported only!");
            return;
        }
        if (!mediaProvider) {
            mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }
        this.mediaProviders.get(mediaProvider).setMicrophoneGain(volume);
    },

    //works only for WSPlayer
    playFirstSound: function () {
        var audioBuffer = this.audioContext.createBuffer(1, 441, 44100);
        var output = audioBuffer.getChannelData(0);
        for (var i = 0; i < output.length; i++) {
            output[i] = Math.random() * 2 - 1;
        }
        var src = this.audioContext.createBufferSource();
        src.buffer = audioBuffer;
        src.connect(this.audioContext.destination);
        src.start(0);
    },

    sendMessage: function (message) {
        var id = createUUID();
        message.id = id;
        message.from = this.userData.sipLogin;
        message.contentType = message.contentType || this.configuration.msgContentType;
        message.isImdnRequired = message.isImdnRequired || this.configuration.imdnEnabled;
        this.messages[id] = message;
        this.webSocket.send("sendMessage", message);
    },

    notificationResult: function (result) {
        this.webSocket.send("notificationResult", result);
    },

    sendData: function (data) {
        this.webSocket.send("sendData", data);
    },

    requestStreamInfo:function(stream) {
        console.log("requestStreamInfo stream " + stream.name);
        this.webSocket.send("requestStreamInfo", stream);
    },

    publishStream: function (stream) {
        var me = this;
        var mediaSessionId = createUUID();

        stream.mediaSessionId = mediaSessionId;
        stream.published = true;
        if (stream.record == undefined) {
            stream.record = false;
        }
        if (stream.hasVideo == undefined) {
            stream.hasVideo = true;
        }
        if (!stream.mediaProvider) {
            stream.mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }

        me.checkAndGetAccess(stream.mediaProvider, stream.hasVideo, function () {
            if (MediaProvider.WebRTC == stream.mediaProvider) {
                me.webRtcMediaManager.newConnection(mediaSessionId, new WebRtcMediaConnection(me.webRtcMediaManager, me.configuration.stunServer, me.configuration.useDTLS, undefined, mediaSessionId));

                me.webRtcMediaManager.createOffer(mediaSessionId, function (sdp) {
                    trace("Publish name " + stream.name);
                    if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                        sdp = me.stripCodecsSDP(sdp, true);
                        console.log("New SDP: " + sdp);
                    }
                    stream.sdp = me.removeCandidatesFromSDP(sdp);
                    me.webSocket.send("publishStream", stream);
                    me.publishStreams.add(stream.mediaSessionId, stream);
                }, true, stream.hasVideo);
            } else if (MediaProvider.Flash == stream.mediaProvider) {
                //todo add pcma/pcmu
                //Priority codec is important because of mediamanager initialize microphone with alaw by default
                stream.sdp = "v=0\r\n" +
                    "o=- 1988962254 1988962254 IN IP4 0.0.0.0\r\n" +
                    "c=IN IP4 0.0.0.0\r\n" +
                    "t=0 0\r\n" +
                    "a=sdplang:en\r\n" +
                    "m=video 0 RTP/AVP 112\r\n" +
                    "a=rtpmap:112 H264/90000\r\n" +
                    "a=fmtp:112 packetization-mode=1; profile-level-id=420020\r\n" +
                    "a=sendonly\r\n" +
                    "m=audio 0 RTP/AVP 8 0 100\r\n" +
                    "a=rtpmap:0 PCMU/8000\r\n" +
                    "a=rtpmap:8 PCMA/8000\r\n" +
                    "a=rtpmap:100 SPEEX/16000\r\n" +
                    "a=sendonly\r\n";
                me.webSocket.send("publishStream", stream);
                me.publishStreams.add(stream.mediaSessionId, stream);

            }
        }, []);

    },

    unPublishStream: function (stream) {
        console.log("Unpublish stream " + stream.name);
        var me = this;
        var removedStream = me.publishStreams.search('name',stream.name);
        if (removedStream) {
            me.publishStreams.remove(removedStream.mediaSessionId);
            if (MediaProvider.WebRTC == removedStream.mediaProvider) {
                me.webRtcMediaManager.close(removedStream.mediaSessionId);
            } else if (MediaProvider.Flash == removedStream.mediaProvider) {
                me.flashMediaManager.unPublishStream(removedStream.mediaSessionId);
            }
            me.webSocket.send("unPublishStream", removedStream);
        }
    },

    shareScreen: function (stream, extensionId) {
        console.log("Share screen with name " + stream.name);
        var me = this;
        var mediaSessionId = createUUID();

        stream.mediaSessionId = mediaSessionId;
        stream.published = true;
        if (stream.record == undefined) {
            stream.record = false;
        }
        if (stream.hasVideo == undefined) {
            stream.hasVideo = true;
        }
        stream.hasAudio = false; 
        stream.mediaProvider = MediaProvider.WebRTC;
        me.getScreenAccess(extensionId, function(response) {
            if (response.success) {
                me.webRtcMediaManager.newConnection(mediaSessionId, new WebRtcMediaConnection(me.webRtcMediaManager, me.configuration.stunServer, me.configuration.useDTLS, undefined, mediaSessionId));

                me.webRtcMediaManager.createOffer(mediaSessionId, function (sdp) {
                    trace("Publish name for screen sharing " + stream.name);
                    if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                        sdp = me.stripCodecsSDP(sdp, true);
                        console.log("New SDP: " + sdp);
                    }
                    stream.sdp = me.removeCandidatesFromSDP(sdp);
                    me.webSocket.send("publishStream", stream);
                    me.publishStreams.add(stream.mediaSessionId, stream);
                }, true, stream.hasVideo, false, true);
            }
        });
    },

    playStream: function (stream) {
        var me = this;
        if (!stream.remoteMediaElementId) {
            var streamObj = me.playStreams.search('name',stream.name);
            if (streamObj) {
                console.log("Request resume for stream " + stream.name);
                if (streamObj.mediaProvider == MediaProvider.WSPlayer) {
                    me.wsPlayerMediaManager.resume();
                }
                me.webSocket.send("playStream", streamObj);
                return;
            }
        }
        var mediaSessionId = createUUID();
        stream.mediaSessionId = mediaSessionId;
        stream.published = false;
        if (stream.record == undefined) {
            stream.record = false;
        }
        if (!stream.mediaProvider) {
            stream.mediaProvider = Object.keys(Flashphoner.getInstance().mediaProviders.getData())[0];
        }


        if (MediaProvider.WebRTC == stream.mediaProvider) {

            me.webRtcMediaManager.newConnection(mediaSessionId, new WebRtcMediaConnection(me.webRtcMediaManager, me.configuration.stunServer, me.configuration.useDTLS, stream.remoteMediaElementId || me.configuration.remoteMediaElementId, mediaSessionId));


            if (stream.hasVideo == undefined) {
                stream.hasVideo = true;
            }

            me.webRtcMediaManager.createOffer(mediaSessionId, function (sdp) {
                console.log("playStream name " + stream.name);
                if (me.configuration.stripCodecs && me.configuration.stripCodecs.length > 0) {
                    sdp = me.stripCodecsSDP(sdp, true);
                    console.log("New SDP: " + sdp);
                }
                stream.sdp = me.removeCandidatesFromSDP(sdp);
                me.webSocket.send("playStream", stream);

                me.playStreams.add(stream.mediaSessionId, stream);
            }, false, false, stream.hasVideo);
            //!stream.sdp is for wsPlayer backward compatibility
        } else if (MediaProvider.Flash == stream.mediaProvider && !stream.sdp){
            //todo add pcma/pcmu
            stream.sdp = "v=0\r\n" +
                "o=- 1988962254 1988962254 IN IP4 0.0.0.0\r\n" +
                "c=IN IP4 0.0.0.0\r\n" +
                "t=0 0\r\n" +
                "a=sdplang:en\r\n"+
                "m=video 0 RTP/AVP 112\r\n" +
                "a=rtpmap:112 H264/90000\r\n" +
                "a=fmtp:112 packetization-mode=1; profile-level-id=420020\r\n" +
                "a=recvonly\r\n" +
                "m=audio 0 RTP/AVP 0 8 100\r\n" +
                "a=rtpmap:0 PCMU/8000\r\n" +
                "a=rtpmap:8 PCMA/8000\r\n" +
                "a=rtpmap:100 SPEEX/16000\r\n" +
                "a=recvonly\r\n";
            me.webSocket.send("playStream", stream);
            me.playStreams.add(stream.mediaSessionId, stream);
        } else if (MediaProvider.WSPlayer == stream.mediaProvider) {
            stream.sdp = "v=0\r\n" +
                "o=- 1988962254 1988962254 IN IP4 0.0.0.0\r\n" +
                "c=IN IP4 0.0.0.0\r\n" +
                "t=0 0\r\n" +
                "a=sdplang:en\r\n" +
                "m=video 0 RTP/AVP 32\r\n" +
                "a=rtpmap:32 MPV/90000\r\n" +
                "a=recvonly\r\n" +
                "m=audio 0 RTP/AVP 0\r\n" +
                "a=rtpmap:0 PCMU/8000\r\n" +
                "a=recvonly\r\n";
            me.webSocket.send("playStream", stream);
            me.playStreams.add(stream.mediaSessionId, stream);
            me.wsPlayerMediaManager.play(stream);
        } else {
            console.log("playStream name " + stream.name);
            me.webSocket.send("playStream", stream);
            me.playStreams.add(stream.mediaSessionId, stream);
        }
        return stream;
    },

    stopStream: function (stream) {
        console.log("unSubscribe stream " + stream.name);
        var me = this;
        var streamObj;
        if (stream.remoteMediaElementId) {
            streamObj = me.playStreams.search('remoteMediaElementId', stream.remoteMediaElementId);
        } else {
            streamObj = me.playStreams.search('name',stream.name);
        }
        if (streamObj) {
            me.playStreams.remove(streamObj.mediaSessionId);
            me.releaseMediaManagerStream(streamObj);
            me.webSocket.send("stopStream", streamObj);
        }
    },

    //Works only with websocket streams
    pauseStream: function (stream) {
        console.log("Pause stream " + stream.name);
        if (MediaProvider.WSPlayer == stream.mediaProvider && this.wsPlayerMediaManager) {
            this.wsPlayerMediaManager.pause();
        }
        this.webSocket.send("pauseStream", stream);
    },

    subscribeRoom:function (roomName, roomEventListener, thisArg) {
        this.roomListeners[roomName] = {func: roomEventListener, thisArg: thisArg};
        this.webSocket.send("subscribeRoom", {name:roomName});
    },

    sendRoomData:function (roomName, data) {
        this.webSocket.send("sendRoomData", {name:roomName, data:data});
    },

    invokeRoomStatusEventListener: function (roomName, argsArray) {
        var listener = this.roomListeners[roomName];
        if (listener) {
            listener.func.apply(listener.thisArg ? listener.thisArg : window, argsArray);
        }
    },

    unsubscribeRoom: function (roomName) {
        delete this.roomListeners[roomName];
        this.webSocket.send("unsubscribeRoom", {name:roomName});
    },

    releaseMediaManagerStream: function (stream) {
        var me = this;
        if (MediaProvider.WebRTC == stream.mediaProvider && me.webRtcMediaManager) {
            me.webRtcMediaManager.close(stream.mediaSessionId);
        } else if (MediaProvider.Flash == stream.mediaProvider && me.flashMediaManager) {
            if (stream.published) {
                me.flashMediaManager.unPublishStream(stream.mediaSessionId);
            } else {
                me.flashMediaManager.stopStream(stream.mediaSessionId);
            }
        } else if (MediaProvider.WSPlayer == stream.mediaProvider && me.wsPlayerMediaManager) {
            me.wsPlayerMediaManager.stop();
        }
    },

    removeCandidatesFromSDP: function (sdp) {
        var sdpArray = sdp.split("\n");

        for (i = 0; i < sdpArray.length; i++) {
            if (sdpArray[i].search("a=candidate:") != -1) {
                sdpArray[i] = "";
            }
        }

        //normalize sdp after modifications
        var result = "";
        for (i = 0; i < sdpArray.length; i++) {
            if (sdpArray[i] != "") {
                result += sdpArray[i] + "\n";
            }
        }

        return result;
    },

    notifyMediaProviderEvent: function (e) {
        if (e.mediaProvider == MediaProvider.WSPlayer) {
            switch (e.status) {
                case "failed":
                    if (this.connection.status != ConnectionStatus.Disconnected) {
                        this.invokeListener(WCSEvent.MediaProviderStatusEvent, [
                            {mediaProvider: MediaProvider.WSPlayer, status: ConnectionStatus.Failed}
                        ]);
                        this.initWSPlayerMediaManager();
                    }
                    break;
                case "closed":
                    if (this.connection.status != ConnectionStatus.Disconnected) {
                        this.invokeListener(WCSEvent.MediaProviderStatusEvent, [
                            {mediaProvider: MediaProvider.WSPlayer, status: ConnectionStatus.Disconnected}
                        ]);
                    }
                    break;
                case "connected":
                    var playStreamsArray = this.playStreams.array();
                    var i;
                    //check if we have active stream
                    for (i = 0; i < playStreamsArray.length; i++) {
                        if (playStreamsArray[i].mediaProvider == MediaProvider.WSPlayer &&
                            playStreamsArray[i].status == StreamStatus.Playing ||
                            playStreamsArray[i].status == StreamStatus.Paused) {
                            this.wsPlayerMediaManager.play(playStreamsArray[i]);
                            break;
                        }
                    }
                    this.invokeListener(WCSEvent.MediaProviderStatusEvent, [
                        {mediaProvider: MediaProvider.WSPlayer, status: ConnectionStatus.Established}
                    ]);
                    break;
            }
        }
    },

    stripCodecsSDP: function (sdp, removeCandidates) {
        var sdpArray = sdp.split("\n");

        //search and delete codecs line
        var pt = [];
        var i;
        for (p = 0; p < this.configuration.stripCodecs.length; p++) {
            console.log("Searching for codec " + this.configuration.stripCodecs[p]);
            for (i = 0; i < sdpArray.length; i++) {
                if (sdpArray[i].search(this.configuration.stripCodecs[p]) != -1 && sdpArray[i].indexOf("a=rtpmap") == 0) {
                    console.log(this.configuration.stripCodecs[p] + " detected");
                    pt.push(sdpArray[i].match(/[0-9]+/)[0]);
                    sdpArray[i] = "";
                }
            }
        }

        if (removeCandidates) {
            for (i = 0; i < sdpArray.length; i++) {
                if (sdpArray[i].search("a=candidate:") != -1) {
                    sdpArray[i] = "";
                }
            }
        }

        if (pt.length) {
            //searching for fmtp
            for (p = 0; p < pt.length; p++) {
                for (i = 0; i < sdpArray.length; i++) {
                    if (sdpArray[i].search("a=fmtp:" + pt[p]) != -1) {
                        console.log("PT " + pt[p] + " detected");
                        sdpArray[i] = "";
                    }
                }
            }

            //delete entries from m= line
            for (i = 0; i < sdpArray.length; i++) {
                if (sdpArray[i].search("m=audio") != -1) {
                    console.log("m line detected " + sdpArray[i]);
                    var mLineSplitted = sdpArray[i].split(" ");
                    var newMLine = "";
                    for (m = 0; m < mLineSplitted.length; m++) {
                        if (pt.indexOf(mLineSplitted[m]) == -1 || m <= 2) {
                            newMLine += mLineSplitted[m];
                            if (m < mLineSplitted.length - 1) {
                                newMLine = newMLine + " ";
                            }
                        }
                    }
                    sdpArray[i] = newMLine;
                    console.log("Resulting m= line is: " + sdpArray[i]);
                    break;
                }
            }
        }

        //normalize sdp after modifications
        var result = "";
        for (i = 0; i < sdpArray.length; i++) {
            if (sdpArray[i] != "") {
                result += sdpArray[i] + "\n";
            }
        }

        return result;
    },

    handleVideoSSRC: function (sdp) {
        var sdpArray = sdp.split("\n");
        var videoPart = false;
        var recvonly = false;
        var ssrcPos = -1;
        for (i = 0; i < sdpArray.length; i++) {
            if (sdpArray[i].search("m=video") != -1) {
                videoPart = true;
            }
            if (sdpArray[i].search("a=ssrc") != -1 && videoPart) {
                ssrcPos = i;
            }
            if (sdpArray[i].search("a=recvonly") != -1 && videoPart) {
                recvonly = true;
            }
            if (sdpArray[i].search("m=audio") != -1 && videoPart) {
                break;
            }
        }

        if (recvonly && ssrcPos != -1) {
            sdpArray[ssrcPos] = "";
        }

        //normalize sdp after modifications
        var result = "";
        for (i = 0; i < sdpArray.length; i++) {
            if (sdpArray[i] != "") {
                result += sdpArray[i] + "\n";
            }
        }

        return result;
    },

    getScreenAccess: function (extensionId, callback) {
        this.webRtcMediaManager.getScreenAccess(extensionId, callback);
    },

    isScreenSharingExtensionInstalled: function(extensionId, callback) {
        if (this.isChrome()) {
            chrome.runtime.sendMessage(extensionId, {type: "isInstalled"}, function (response) {
                if (response) {
                    callback(true);
                } else {
                    callback(false);
                }
            });
        } else if (this.isFF()) {
            callback(this.firefoxScreenSharingExtensionInstalled);
        }
    },

    checkAndGetAccess: function (mediaProvider, hasVideo, func, args) {
        var me = this;
        if (args === undefined) {
            args = [];
        }
        var localMediaVideoSourceId = Flashphoner.getInstance().configuration.videoSourceId;
        if (localMediaVideoSourceId != null && localMediaVideoSourceId != lastVideoSourceId) {
            trace("Video source was changed from " + lastVideoSourceId + " to " + localMediaVideoSourceId);
            me.releaseCameraAndMicrophone(mediaProvider);
            me.webRtcMediaManager.localAudioVideoStream = undefined;
        }

        if (!this.hasAccess(mediaProvider, hasVideo)) {
            if (this.intervalId == -1) {
                var checkAccessFunc = function () {
                    if (me.hasAccess(mediaProvider, hasVideo)) {
                        clearInterval(me.intervalId);
                        me.intervalId = -1;
                        me.checkAndGetAccess(mediaProvider, hasVideo, func, args);
                    }
                };
                this.intervalId = setInterval(checkAccessFunc, 500);
            }
            this.getAccess(mediaProvider, hasVideo);
        } else if (this.hasAccess(mediaProvider, hasVideo)) {
            func.apply(this, args);
        } else {
            trace("Microphone is not plugged in");
        }
    },

    getCookie: function (c_name) {
        var i, x, y, ARRcookies = document.cookie.split(";");
        for (i = 0; i < ARRcookies.length; i++) {
            x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
            x = x.replace(/^\s+|\s+$/g, "");
            if (x == c_name) {
                return ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
            }
        }
        return "";
    },

    setCookie: function (c_name, value) {
        var exdate = new Date();
        exdate.setDate(exdate.getDate() + 100);
        var c_value = escape(value) + "; expires=" + exdate.toUTCString();
        document.cookie = c_name + "=" + c_value;
        return value;
    },

    getWsPlayerLastVideoFrame: function () {
        if (this.wsPlayerMediaManager && this.wsPlayerMediaManager.getLastVideoFrame) {
            return this.wsPlayerMediaManager.getLastVideoFrame();
        }
    }
};

var isFlashphonerAPILoaded = false;
function notifyFlashphonerAPILoaded() {
    isFlashphonerAPILoaded = true;
    Flashphoner.getInstance().initFlashMediaManager();
}

var WebRtcMediaManager = function () {
    this.webRtcMediaConnections = new DataMap();
    this.audioMuted = 1;
    this.videoMuted = 1;
    this.remoteSDP = {};
    this.onLocalScreenMediaStreamEnded = null;
};

WebRtcMediaManager.prototype.getVolume = function (id) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    return webRtcMediaConnection.remoteMediaElement.volume;
};

WebRtcMediaManager.prototype.setVolume = function (id, volume) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    webRtcMediaConnection.remoteMediaElement.volume = volume / 100;
};

WebRtcMediaManager.prototype.setMicrophoneGain = function (volume) {
    if (this.microphoneGain)
        this.microphoneGain.gain.value = volume;
};

WebRtcMediaManager.prototype.isVideoMuted = function () {
    if (this.localAudioVideoStream) {
        return !this.localAudioVideoStream.getVideoTracks()[0].enabled;
    } else {
        return true;
    }
};

WebRtcMediaManager.prototype.muteVideo = function () {
    if (this.localAudioVideoStream) {
        this.localAudioVideoStream.getVideoTracks()[0].enabled = false;
    }
};

WebRtcMediaManager.prototype.unmuteVideo = function () {
    if (this.localAudioVideoStream && this.localAudioVideoStream.getVideoTracks().length > 0) {
        this.localAudioVideoStream.getVideoTracks()[0].enabled = true;
    }
};

WebRtcMediaManager.prototype.mute = function () {
    if (this.localAudioStream) {
        this.localAudioStream.getAudioTracks()[0].enabled = false;
    }
    if (this.localAudioVideoStream) {
        this.localAudioVideoStream.getAudioTracks()[0].enabled = false;
    }
};

WebRtcMediaManager.prototype.unmute = function () {
    if (this.localAudioStream) {
        this.localAudioStream.getAudioTracks()[0].enabled = true;
    }
    if (this.localAudioVideoStream) {
        this.localAudioVideoStream.getAudioTracks()[0].enabled = true;
    }
};

WebRtcMediaManager.prototype.hasAccessToAudio = function () {
    return this.audioMuted == -1;
};

WebRtcMediaManager.prototype.hasAccessToAudioAndVideo = function () {
    return this.videoMuted == -1;
};

WebRtcMediaManager.prototype.newConnection = function (id, webRtcMediaConnection) {
    if (this.remoteSDP[id] || this.remoteSDP[id] == "") {
        webRtcMediaConnection.setRemoteSDP(this.remoteSDP[id], false);
        delete this.remoteSDP[id];
    }
    this.webRtcMediaConnections.add(id, webRtcMediaConnection);
};

WebRtcMediaManager.prototype.receivedEmptyRemoteSDP = function (id) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    return !webRtcMediaConnection || webRtcMediaConnection.lastReceivedSdp == "";
};

WebRtcMediaManager.prototype.createOffer = function (id, callback, hasAudio, hasVideo, receiveVideo, screenCapture) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    webRtcMediaConnection.createOffer(callback, hasAudio, hasVideo, receiveVideo, screenCapture);
};

WebRtcMediaManager.prototype.createAnswer = function (id, callback, hasVideo) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    webRtcMediaConnection.createAnswer(callback, hasVideo);
};

WebRtcMediaManager.prototype.setRemoteSDP = function (id, sdp, isInitiator) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(id);
    if (webRtcMediaConnection) {
        webRtcMediaConnection.setRemoteSDP(sdp, isInitiator);
    } else {
        this.remoteSDP[id] = sdp;
    }
};

WebRtcMediaManager.prototype.getStatistics = function (callId, callbackFn) {
    var webRtcMediaConnection = this.webRtcMediaConnections.get(callId);
    webRtcMediaConnection.getStatistics(callbackFn);
};


WebRtcMediaManager.prototype.setAudioCodec = function (id, codec) {
};

WebRtcMediaManager.prototype.talk = function (callId, hasVideo) {

};

WebRtcMediaManager.prototype.hold = function (callId) {
};

WebRtcMediaManager.prototype.close = function (id) {
    var connection = this.webRtcMediaConnections.remove(id);
    if (connection) {
        connection.close();
        this.unmute();
        this.unmuteVideo();
    }
};

WebRtcMediaManager.prototype.disconnect = function () {
    for (var id in this.webRtcMediaConnections.getData()) {
        this.webRtcMediaConnections.remove(id).close();
    }
};


function createGainNode(stream) {
    var audioCtx = Flashphoner.getInstance().audioContext;
    var source = audioCtx.createMediaStreamSource(stream);
    var gainNode = audioCtx.createGain();
    var destination = audioCtx.createMediaStreamDestination();
    var outputStream = destination.stream;
    // source -> gainNode -> destination -> peerConnection
    source.connect(gainNode);
    gainNode.connect(destination);
    // replace audiotrack to new which contain gainNode
    var newTrack = outputStream.getAudioTracks()[0];
    stream.addTrack(newTrack);
    var originalTrack = stream.getAudioTracks()[0];
    stream.removeTrack(originalTrack);
    return gainNode;
}

var lastVideoSourceId;
WebRtcMediaManager.prototype.getAccessToAudioAndVideo = function () {
    var me = this;
    if (!me.localAudioVideoStream) {
        var requestedMedia = {};
        requestedMedia.audio = true;
        requestedMedia.video = {};
        //FF differs from Chrome
        if (webrtcDetectedBrowser == "firefox") {
            requestedMedia.video.width = Flashphoner.getInstance().configuration.videoWidth;
            requestedMedia.video.height = Flashphoner.getInstance().configuration.videoHeight;
            if (Flashphoner.getInstance().configuration.videoSourceId != null) {
                requestedMedia.video.optional = [{sourceId: Flashphoner.getInstance().configuration.videoSourceId}];
                lastVideoSourceId = Flashphoner.getInstance().configuration.videoSourceId;
            }
        } else {
            requestedMedia.video = {
                mandatory: {
                    maxWidth: Flashphoner.getInstance().configuration.videoWidth,
                    maxHeight: Flashphoner.getInstance().configuration.videoHeight
                },
                optional: []
            };

            if (Flashphoner.getInstance().configuration.videoSourceId != null) {
                requestedMedia.video.optional = [{sourceId: Flashphoner.getInstance().configuration.videoSourceId}];
                lastVideoSourceId = Flashphoner.getInstance().configuration.videoSourceId;
            }

            if (Flashphoner.getInstance().configuration.forceResolution) {
                requestedMedia.video.mandatory.minWidth = Flashphoner.getInstance().configuration.videoWidth;
                requestedMedia.video.mandatory.minHeight = Flashphoner.getInstance().configuration.videoHeight;
            }
        }
        var mediaStream = function (stream) {
            var localMediaElement = getElement(Flashphoner.getInstance().configuration.localMediaElementId);
            if (localMediaElement) {
                attachMediaStream(localMediaElement, stream);
            }
            me.localAudioVideoStream = stream;

            if (webrtcDetectedBrowser == "chrome" && Flashphoner.getInstance().configuration.gainControllerEnabled) {
                me.microphoneGain = createGainNode(stream);
            }

            if (webrtcDetectedBrowser != "firefox") {
                me.audioMuted = -1;
            }
            me.videoMuted = -1;
        };
        var error = function (error) {
            trace("Failed to get access to local media. Error code was " + error.code + ".");
            me.audioMuted = 1;
            me.videoMuted = 1;
            var status = {
                status: WCSError.MIC_CAM_ACCESS_PROBLEM,
                info: "Failed to get access to microphone and camera. Error code was " + error.code + "."
            };
            Flashphoner.getInstance().invokeProblem(status);
        };
        if (Flashphoner.getInstance().checkMediaDevices()) {
            navigator.mediaDevices.getUserMedia(requestedMedia)
                .then(mediaStream)
                .catch(error);
        } else {
            getUserMedia(requestedMedia, mediaStream, error);
        }
    }
    return true;
};

WebRtcMediaManager.prototype.getScreenAccess = function (extensionId, callback) {
    var me = this;
    if (Flashphoner.getInstance().isChrome()) {
        chrome.runtime.sendMessage(extensionId, {type: "isInstalled"}, function (response) {
            if (response) {
                chrome.runtime.sendMessage(extensionId, {type: "getSourceId"}, function (response) {
                    if (response.error) {
                        var status = {
                            status: WCSError.SCREEN_ACCESS_PROBLEM,
                            info: "Permission denied"
                        };
                        Flashphoner.getInstance().invokeProblem(status);
                    } else {
                        var screen_constraints = {
                            audio: false,
                            video: {
                                mandatory: {
                                    maxWidth: Flashphoner.getInstance().configuration.screenSharingVideoWidth,
                                    maxHeight: Flashphoner.getInstance().configuration.screenSharingVideoHeight,
                                    chromeMediaSourceId: response.sourceId,
                                    chromeMediaSource: "desktop"
                                },
                                optional: []
                            }
                        };
                        if (Flashphoner.getInstance().configuration.screenSharingVideoFps) {
                            screen_constraints.video.mandatory.maxFrameRate = Flashphoner.getInstance().configuration.screenSharingVideoFps;
                        }
                        var mediaStream = function (stream) {
                            var localMediaElement2 = getElement(Flashphoner.getInstance().configuration.localMediaElementId2);
                            if (localMediaElement2) {
                                attachMediaStream(localMediaElement2, stream);
                            }
                            me.localScreenCaptureStream = stream;
                            callback({success: true});
                        };
                        var error = function (error) {
                            trace("Failed to get access to screen capture. Error code was " + error.code + ".");
                            callback({success: false});
                            var status = {
                                status: WCSError.SCREEN_ACCESS_PROBLEM,
                                info: "Failed to get access to screen capture. Error code was " + error.code + "."
                            };
                            Flashphoner.getInstance().invokeProblem(status);
                        };
                        if (Flashphoner.getInstance().checkMediaDevices()) {
                            navigator.mediaDevices.getUserMedia(screen_constraints)
                                .then(mediaStream)
                                .catch(error);
                        } else {
                            getUserMedia(screen_constraints, mediaStream, error);
                        };
                    }
                });
            } else {
                var status = {
                    status: WCSError.SCREEN_EXTENSION_UNAVAILABLE,
                    info: "Screen sharing extension not available!"
                };
                Flashphoner.getInstance().invokeProblem(status);
            }
        });
    } else if (Flashphoner.getInstance().isFF()) {
        if (Flashphoner.getInstance().firefoxScreenSharingExtensionInstalled) {
            var constraints = {
                video: {
                    //can be screen, window or application
                    //todo add to method arguments
                    mediaSource: 'window',
                    mandatory: {
                        maxWidth: Flashphoner.getInstance().configuration.screenSharingVideoWidth,
                        maxHeight: Flashphoner.getInstance().configuration.screenSharingVideoHeight
                    }
                }
            };
            if (Flashphoner.getInstance().configuration.screenSharingVideoFps) {
                constraints.video.mandatory.maxFrameRate = Flashphoner.getInstance().configuration.screenSharingVideoFps;
            }
            var mediaStream = function (stream) {
                var localMediaElement2 = getElement(Flashphoner.getInstance().configuration.localMediaElementId2);
                if (localMediaElement2) {
                    attachMediaStream(localMediaElement2, stream);
                }
                me.localScreenCaptureStream = stream;
                callback({success: true});
            };
            var error = function (error) {
                trace("Failed to get access to screen capture. Error code was " + error.code + ".");
                callback({success: false});
                var status = {
                    status: WCSError.SCREEN_ACCESS_PROBLEM,
                    info: "Failed to get access to screen capture. Error code was " + error.code + "."
                };
                Flashphoner.getInstance().invokeProblem(status);
            };
            if (Flashphoner.getInstance().checkMediaDevices()) {
                trace("");
                navigator.mediaDevices.getUserMedia(constraints)
                    .then(mediaStream)
                    .catch(error);
            } else {
                getUserMedia(constraints, mediaStream, error);
            }
        } else {
            var status = {
                status: WCSError.SCREEN_EXTENSION_UNAVAILABLE,
                info: "Screen sharing extension not available!"
            };
            Flashphoner.getInstance().invokeProblem(status);
        }
    } else {
        var status = {
            status: WCSError.SCREEN_EXTENSION_UNAVAILABLE,
            info: "Screen sharing is not supported in this browser!"
        };
        Flashphoner.getInstance().invokeProblem(status);
    }
};

WebRtcMediaManager.prototype.getAccessToAudio = function () {
    var me = this;
    if (!me.localAudioStream) {
        var mediaStream = function (stream) {
            me.localAudioStream = stream;
            me.audioMuted = -1;
        };
        var error = function (error) {
            var status = {
                status: WCSError.MIC_ACCESS_PROBLEM,
                info: "Failed to get access to microphone. Error code was " + error.code + "."
            };
            Flashphoner.getInstance().invokeProblem(status);
            me.audioMuted = 1;
        };
        if (Flashphoner.getInstance().checkMediaDevices()) {
            navigator.mediaDevices.getUserMedia({audio: true})
                .then(mediaStream)
                .catch(error);
        } else {
            getUserMedia({audio: true}, mediaStream, error);
        }
    }
    return true;
};

WebRtcMediaManager.prototype.releaseCameraAndMicrophone = function () {
    if (this.localAudioStream) {
        this.localAudioStream.stop();
        this.localAudioStream = null;
    }
    if (this.localAudioVideoStream) {
        this.localAudioVideoStream.stop();
        this.localAudioVideoStream = null;
    }
    if (this.localScreenCaptureStream) {
        this.localScreenCaptureStream.stop();
        this.localScreenCaptureStream = null;
    }
    this.audioMuted = 1;
    this.videoMuted = 1;
};

var WebRtcMediaConnection = function (webRtcMediaManager, stunServer, useDTLS, remoteMediaElementId, id) {
    var me = this;
    me.webRtcMediaManager = webRtcMediaManager;
    me.peerConnection = null;
    me.peerConnectionState = 'new';
    me.remoteAudioVideoMediaStream = null;
    if (remoteMediaElementId) {
        me.remoteMediaElement = getElement(remoteMediaElementId);
    }
    me.stunServer = stunServer;

    //If we set false immediately, we use false, if it is undefined
    if (useDTLS===false || useDTLS==='false'){
        me.useDTLS = false;
    }else{
        me.useDTLS = true;
    }

    me.lastReceivedSdp = null;
    me.id = id;
    //stun server by default
    //commented to speedup WebRTC call establishment
    //me.stunServer = "stun.l.google.com:19302";
};

WebRtcMediaConnection.prototype.init = function () {
    trace("WebRtcMediaConnection - init");
    this.hasVideo = false;
    this.peerConnection = null;
    this.peerConnectionState = 'new';
    this.remoteAudioVideoMediaStream = null;
};

WebRtcMediaConnection.prototype.close = function () {
    //Commented to prevent termination of rtcMediaManager after MSRP call
    trace("WebRtcMediaConnection - close()");
    if (this.peerConnectionState != 'finished') {
        this.peerConnectionState = 'finished';
        if (this.peerConnection) {
            trace("WebRtcMediaConnection - PeerConnection will be closed");
            if (this.remoteMediaElement) {
                this.remoteMediaElement.pause();
            }
            //check if this was screen sharing media and close it
            if (this.webRtcMediaManager.localScreenCaptureStream) {
                //todo use this.peerConnection.getStreamById() when available in firefox
                var localStreams = this.peerConnection.getLocalStreams();
                var me = this;
                localStreams.some(function (mediaStream) {
                    if (me.webRtcMediaManager.localScreenCaptureStream.id == mediaStream.id) {
                        me.webRtcMediaManager.localScreenCaptureStream.getVideoTracks()[0].stop();
                        me.webRtcMediaManager.localScreenCaptureStream = null;
                        return true;
                    }
                })
            }
            this.peerConnection.close();
        }
    } else {
        console.log("peerConnection already closed, do nothing!");
    }
};


WebRtcMediaConnection.prototype.createPeerConnection = function () {
    trace("WebRtcMediaConnection - createPeerConnection()");
    var application = this;
    if (application.stunServer !== undefined && application.stunServer.length > 0) {
        pc_config = {
            "iceServers": [
                {"url": "stun:" + application.stunServer}
            ]
        };
    } else {
        pc_config = {"iceServers": []};
    }
    this.peerConnection = new RTCPeerConnection(pc_config, {
        "optional": [
            {"DtlsSrtpKeyAgreement": application.useDTLS}
        ]
    });

    this.peerConnection.onaddstream = function (event) {
        application.onOnAddStreamCallback(event);
    };


    this.peerConnection.onremovestream = function (event) {
        application.onOnRemoveStreamCallback(event);
    };
};

WebRtcMediaConnection.prototype.onOnAddStreamCallback = function (event) {
    trace("WebRtcMediaConnection - onOnAddStreamCallback(): event=" + event);
    trace("WebRtcMediaConnection - onOnAddStreamCallback(): event=" + event.stream);
    trace("WebRtcMediaConnection - onOnAddStreamCallback(): event=" + this.remoteMediaElement);
    if (this.peerConnection != null) {
        this.remoteAudioVideoMediaStream = event.stream;
        if (this.remoteMediaElement) {
            attachMediaStream(this.remoteMediaElement, this.remoteAudioVideoMediaStream);
            var playPromise = this.remoteMediaElement.play();
            if (playPromise !== undefined) {
                playPromise.catch(function (error) {
                    trace(error);
                });
            }
        }
    }
    else {
        console.warn("SimpleWebRtcSipPhone:onOnAddStreamCallback(): this.peerConnection is null, bug in state machine!, bug in state machine!");
    }
};

WebRtcMediaConnection.prototype.onOnRemoveStreamCallback = function (event) {
    trace("WebRtcMediaConnection - onOnRemoveStreamCallback(): event=" + event);
    if (this.peerConnection != null) {
        this.remoteAudioVideoMediaStream = null;
        if (this.remoteMediaElement) {
            this.remoteMediaElement.pause();
        }
    } else {
        console.warn("SimpleWebRtcSipPhone:onOnRemoveStreamCallback(): this.peerConnection is null, bug in state machine!");
    }
};

WebRtcMediaConnection.prototype.waitGatheringIce = function () {
    var me = this;
    if (me.peerConnection != null) {
        sendSdp = function () {
            if (me.peerConnection != null) {
                trace("WebRtcMediaConnection - waitGatheringIce() iceGatheringState=" + me.peerConnection.iceGatheringState);
                if (me.peerConnection.iceGatheringState == "complete") {
                    trace("WebRtcMediaConnection - setLocalSDP: sdp=" + me.peerConnection.localDescription.sdp);
                    if (me.peerConnectionState == 'preparing-offer') {
                        me.peerConnectionState = 'offer-sent';
                        me.createOfferCallback(me.peerConnection.localDescription.sdp);// + this.candidates);
                    }
                    else if (me.peerConnectionState == 'preparing-answer') {
                        me.peerConnectionState = 'established';
                        me.createAnswerCallback(me.peerConnection.localDescription.sdp);// + this.candidates);
                    }
                    else if (me.peerConnectionState == 'established') {
                    }
                    else {
                        console.log("WebRtcMediaConnection - onIceCandidateCallback(): RTCPeerConnection bad state!");
                    }
                    clearInterval(me.iceIntervalId);
                }
            } else {
                clearInterval(me.iceIntervalId);
            }
        };
        me.iceIntervalId = setInterval(sendSdp, 500);

    }
    else {
        console.warn("WebRtcMediaConnection - onIceCandidateCallback(): this.peerConnection is null, bug in state machine!");
    }
};

WebRtcMediaConnection.prototype.getConstraints = function (receiveVideo, screenCapture) {
    var constraints = {};
    if (webrtcDetectedBrowser == "firefox") {
        constraints = {offerToReceiveAudio: !screenCapture, offerToReceiveVideo: receiveVideo};
    } else {
        constraints = {optional: [], mandatory: {OfferToReceiveAudio: true, OfferToReceiveVideo: receiveVideo}};
    }
    return constraints;
};

WebRtcMediaConnection.prototype.createOffer = function (createOfferCallback, hasAudio, hasVideo, receiveVideo, screenCapture) {
    trace("WebRtcMediaConnection - createOffer()");
    var me = this;
    try {
        if (me.getConnectionState() != "established") {
            trace("Connection state is not established. Initializing...");
            me.init();
        }
        var mandatory = {};
        if (me.peerConnection == null) {
            trace("peerConnection is null");
            me.createPeerConnection();
            if (screenCapture) {
                if (me.webRtcMediaManager.localScreenCaptureStream) {
                    me.peerConnection.addStream(me.webRtcMediaManager.localScreenCaptureStream);
                    mandatory = me.getConstraints(receiveVideo, screenCapture);
                    me.webRtcMediaManager.localScreenCaptureStream.onended = function(event) {
                        if (me.peerConnectionState != 'finished') {
                            me.webRtcMediaManager.onLocalScreenMediaStreamEnded(me.id);
                        }
                    }
                }
            } else if (hasAudio && hasVideo) {
                if (me.webRtcMediaManager.videoTrack) {
                    me.webRtcMediaManager.localAudioVideoStream.addTrack(me.webRtcMediaManager.videoTrack);
                    me.webRtcMediaManager.videoTrack = null;
                }
                me.peerConnection.addStream(me.webRtcMediaManager.localAudioVideoStream);
            } else if (hasAudio) {
                if (me.webRtcMediaManager.localAudioStream) {
                    me.peerConnection.addStream(me.webRtcMediaManager.localAudioStream);
                } else {
                    var localAudioVideoStream = me.webRtcMediaManager.localAudioVideoStream;
                    if (localAudioVideoStream.getVideoTracks().length > 0) {
                        me.webRtcMediaManager.videoTrack = localAudioVideoStream.getVideoTracks()[0];
                        localAudioVideoStream.removeTrack(me.webRtcMediaManager.videoTrack);
                    }
                    me.peerConnection.addStream(me.webRtcMediaManager.localAudioVideoStream);
                }
                mandatory = me.getConstraints(receiveVideo);
            } else {
                if (receiveVideo == undefined) {
                    receiveVideo = true;
                }
                mandatory = me.getConstraints(receiveVideo);
            }
        }
        me.createOfferCallback = createOfferCallback;
        me.peerConnection.createOffer(function (offer) {
            me.onCreateOfferSuccessCallback(offer);
        }, function (error) {
            me.onCreateOfferErrorCallback(error);
        }, mandatory);

    }
    catch (exception) {
        console.error("WebRtcMediaConnection - createOffer(): catched exception:" + exception);
    }
};

WebRtcMediaConnection.prototype.createAnswer = function (createAnswerCallback, hasVideo) {
    var me = this;
    trace("WebRtcMediaConnection - createAnswer() me.getConnectionState(): " + me.getConnectionState() + " me.hasVideo: " + me.hasVideo);
    if (me.getConnectionState() != "established") {
        me.init();
    }
    try {

        if (me.peerConnection == null) {
            me.createPeerConnection();
            if (hasVideo) {
                if (me.webRtcMediaManager.videoTrack) {
                    me.webRtcMediaManager.localAudioVideoStream.addTrack(me.webRtcMediaManager.videoTrack);
                    me.webRtcMediaManager.videoTrack = null;
                }
                me.peerConnection.addStream(me.webRtcMediaManager.localAudioVideoStream);
            } else {
                if (me.webRtcMediaManager.localAudioStream) {
                    me.peerConnection.addStream(me.webRtcMediaManager.localAudioStream);
                } else {
                    var localAudioVideoStream = me.webRtcMediaManager.localAudioVideoStream;
                    if (localAudioVideoStream.getVideoTracks().length > 0) {
                        me.webRtcMediaManager.videoTrack = localAudioVideoStream.getVideoTracks()[0];
                        localAudioVideoStream.removeTrack(me.webRtcMediaManager.videoTrack);
                    }
                    me.peerConnection.addStream(me.webRtcMediaManager.localAudioVideoStream);
                }
            }
        }
        me.createAnswerCallback = createAnswerCallback;

        var sdpOffer = new RTCSessionDescription({
            type: 'offer',
            sdp: me.lastReceivedSdp
        });
        me.peerConnectionState = 'offer-received';
        me.peerConnection.setRemoteDescription(sdpOffer, function () {
            me.onSetRemoteDescriptionSuccessCallback();
        }, function (error) {
            me.onSetRemoteDescriptionErrorCallback(error);
        });
    }
    catch (exception) {
        console.error("WebRtcMediaConnection - createAnswer(): catched exception:" + exception);
    }
};

WebRtcMediaConnection.prototype.onCreateOfferSuccessCallback = function (offer) {
    trace("WebRtcMediaConnection - onCreateOfferSuccessCallback this.peerConnection: " + this.peerConnection + " this.peerConnectionState: " + this.peerConnectionState);
    if (this.peerConnection != null) {
        if (this.peerConnectionState == 'new' || this.peerConnectionState == 'established') {
            var application = this;
            this.peerConnectionState = 'preparing-offer';
            this.peerConnection.setLocalDescription(offer, function () {
                application.onSetLocalDescriptionSuccessCallback(offer.sdp);
            }, function (error) {
                application.onSetLocalDescriptionErrorCallback(error);
            });
        }
        else {
            console.error("WebRtcMediaConnection - onCreateOfferSuccessCallback(): RTCPeerConnection bad state!");
        }
    }
    else {
        console.warn("SimpleWebRtcSipPhone:onCreateOfferSuccessCallback(): this.peerConnection is null, bug in state machine!");
    }
};

WebRtcMediaConnection.prototype.onSetLocalDescriptionSuccessCallback = function (sdp) {
    trace("WebRtcMediaConnection - onSetLocalDescriptionSuccessCallback");
    if (webrtcDetectedBrowser == "firefox") {
        trace("WebRtcMediaConnection - onSetLocalDescriptionSuccessCallback: sdp=" + sdp);
        if (this.peerConnectionState == 'preparing-offer') {
            trace("Current PeerConnectionState is 'preparing-offer' sending offer...");
            this.peerConnectionState = 'offer-sent';
            this.createOfferCallback(sdp);
        }
        else if (this.peerConnectionState == 'preparing-answer') {
            trace("Current PeerConnectionState is 'preparing-answer' going to established...");
            this.peerConnectionState = 'established';
            this.createAnswerCallback(sdp);
        }
    } else {
        this.waitGatheringIce();
    }
};

WebRtcMediaConnection.prototype.getConnectionState = function () {
    return this.peerConnectionState;
};

WebRtcMediaConnection.prototype.setRemoteSDP = function (sdp, isInitiator) {
    if (webrtcDetectedBrowser == "chrome" && (Flashphoner.getInstance().configuration.maxBitRate != null || Flashphoner.getInstance().configuration.minBitRate != null)) {
        // Search VP8 payload in SDP
        var a = sdp.split("\r\n");
        var attr = "a=fmtp:";
        for (var i=0; i< a.length; i++) {
            if (/a=rtpmap:\d+ VP8.*/gi.test(a[i])) {
                attr += a[i].replace(/(a=rtpmap:)(\d+)( VP8.*)/,'$2');
            }
        }
        if (Flashphoner.getInstance().configuration.minBitRate != null) {
            attr += " x-google-min-bitrate=" + Flashphoner.getInstance().configuration.minBitRate + ";";
        }
        if (Flashphoner.getInstance().configuration.maxBitRate != null) {
            attr += " x-google-max-bitrate=" + Flashphoner.getInstance().configuration.maxBitRate + ";";
        }
        attr += "\r\n";
        trace("Appending bitrate limit: " + attr);
        sdp = sdp.replace(/(a=rtpmap:100 .*\r\n)/g, '$1'+attr);
    }
    trace("WebRtcMediaConnection - setRemoteSDP: isInitiator: " + isInitiator + " sdp=" + sdp);
    if (isInitiator) {
        var sdpAnswer = new RTCSessionDescription({
            type: 'answer',
            sdp: sdp
        });
        var application = this;
        this.peerConnectionState = 'answer-received';
        this.peerConnection.setRemoteDescription(sdpAnswer, function () {
            application.onSetRemoteDescriptionSuccessCallback();
        }, function (error) {
            application.onSetRemoteDescriptionErrorCallback(error);
        });
    } else {
        this.lastReceivedSdp = sdp;
    }
};

WebRtcMediaConnection.prototype.onSetRemoteDescriptionSuccessCallback = function () {
    trace("onSetRemoteDescriptionSuccessCallback");
    if (this.peerConnection != null) {
        if (this.peerConnectionState == 'answer-received') {
            trace("Current PeerConnectionState is 'answer-received' changing the PeerConnectionState to 'established'");
            this.peerConnectionState = 'established';
        }
        else if (this.peerConnectionState == 'offer-received') {
            trace("Current PeerConnectionState is 'offer-received' creating appropriate answer...");
            var application = this;
            this.peerConnection.createAnswer(function (answer) {
                application.onCreateAnswerSuccessCallback(answer);
            }, function (error) {
                application.onCreateAnswerErrorCallback(error);
            });
        }
        else {
            console.log("WebRtcMediaConnection - onSetRemoteDescriptionSuccessCallback(): RTCPeerConnection bad state!");
        }
    }
    else {
        console.warn("SimpleWebRtcSipPhone:onSetRemoteDescriptionSuccessCallback(): this.peerConnection is null, bug in state machine!");
    }
};


WebRtcMediaConnection.prototype.onCreateAnswerSuccessCallback = function (answer) {
    trace("onCreateAnswerSuccessCallback " + this.peerConnection);
    if (this.peerConnection != null) {
        if (this.peerConnectionState == 'offer-received') {
            trace("Current PeerConnectionState is 'offer-received', preparing answer...");
            // Prepare answer.
            var application = this;
            this.peerConnectionState = 'preparing-answer';
            this.peerConnection.setLocalDescription(answer, function () {
                application.onSetLocalDescriptionSuccessCallback(answer.sdp);
            }, function (error) {
                application.onSetLocalDescriptionErrorCallback(error);
            });
        }
        else {
            console.log("WebRtcMediaConnection - onCreateAnswerSuccessCallback(): RTCPeerConnection bad state!");
        }
    }
    else {
        console.warn("SimpleWebRtcSipPhone:onCreateAnswerSuccessCallback(): this.peerConnection is null, bug in state machine!");
    }
};

WebRtcMediaConnection.prototype.getStatistics = function (callbackFn) {
    var me = this;
    if (this.peerConnection && this.peerConnection.getRemoteStreams()[0] && webrtcDetectedBrowser == "chrome") {
        if (this.peerConnection.getStats) {
            this.peerConnection.getStats(function (rawStats) {
                var results = rawStats.result();
                var result = {type: "chrome", outgoingStreams: {}, incomingStreams: {}};
                for (var i = 0; i < results.length; ++i) {
                    var resultPart = me.processGoogRtcStatsReport(results[i]);
                    if (resultPart != null) {
                        if (resultPart.type == "googCandidatePair") {
                            result.activeCandidate = resultPart;
                        } else if (resultPart.type == "ssrc") {
                            if (resultPart.transportId.indexOf("audio") > -1) {
                                if (resultPart.id.indexOf("send") > -1) {
                                    result.outgoingStreams.audio = resultPart;
                                } else {
                                    result.incomingStreams.audio = resultPart;
                                }

                            } else {
                                if (resultPart.id.indexOf("send") > -1) {
                                    result.outgoingStreams.video = resultPart;
                                } else {
                                    result.incomingStreams.video = resultPart;
                                }

                            }
                        }
                    }
                }
                callbackFn(result);
            }, function (error) {
                console.log("Error received " + error);
            });

        }
    } else if (this.peerConnection && this.peerConnection.getRemoteStreams()[0] && webrtcDetectedBrowser == "firefox") {
        if (this.peerConnection.getStats) {
            this.peerConnection.getStats(null, function (rawStats) {
                var result = {type: "firefox", outgoingStreams: {}, incomingStreams: {}};
                for (var k in rawStats) {
                    if (rawStats.hasOwnProperty(k)) {
                        var resultPart = me.processRtcStatsReport(rawStats[k]);
                        if (resultPart != null) {
                            if (resultPart.type == "outboundrtp") {
                                if (resultPart.id.indexOf("audio") > -1) {
                                    result.outgoingStreams.audio = resultPart;
                                } else {
                                    result.outgoingStreams.video = resultPart;
                                }
                            } else if (resultPart.type == "inboundrtp") {
                                if (resultPart.id.indexOf("audio") > -1) {
                                    result.incomingStreams.audio = resultPart;
                                } else {
                                    result.incomingStreams.video = resultPart;
                                }
                            }
                        }
                    }
                }
                callbackFn(result);
            }, function (error) {
                console.log("Error received " + error);
            });
        }
    }
};

WebRtcMediaConnection.prototype.processRtcStatsReport = function (report) {
    /**
     * RTCStatsReport http://mxr.mozilla.org/mozilla-central/source/dom/webidl/RTCStatsReport.webidl
     */
    var result = null;
    if (report.type && (report.type == "outboundrtp" || report.type == "inboundrtp") && report.id.indexOf("rtcp") == -1) {
        result = {};
        for (var k in report) {
            if (report.hasOwnProperty(k)) {
                result[k] = report[k];
            }
        }
    }

    return result;
};

WebRtcMediaConnection.prototype.processGoogRtcStatsReport = function (report) {
    /**
     * Report types: googComponent, googCandidatePair, googCertificate, googLibjingleSession, googTrack, ssrc
     */
    var gotResult = false;
    var result = null;
    if (report.type && report.type == "googCandidatePair") {
        //check if this is active pair
        if (report.stat("googActiveConnection") == "true") {
            gotResult = true;
        }
    }

    if (report.type && report.type == "ssrc") {
        gotResult = true;
    }

    if (gotResult) {
        //prepare object
        result = {};
        result.timestamp = report.timestamp;
        result.id = report.id;
        result.type = report.type;
        if (report.names) {
            var names = report.names();
            for (var i = 0; i < names.length; ++i) {
                var attrName = names[i];
                result[attrName] = report.stat(attrName);
            }
        }
    }
    return result;
};

WebRtcMediaConnection.prototype.onCreateAnswerErrorCallback = function (error) {
    console.error("WebRtcMediaConnection - onCreateAnswerErrorCallback(): error: " + error);
};
WebRtcMediaConnection.prototype.onCreateOfferErrorCallback = function (error) {
    console.error("WebRtcMediaConnection - onCreateOfferErrorCallback(): error: " + error);
};
WebRtcMediaConnection.prototype.onSetLocalDescriptionErrorCallback = function (error) {
    console.error("WebRtcMediaConnection - onSetLocalDescriptionErrorCallback(): error: " + error);
};
WebRtcMediaConnection.prototype.onSetRemoteDescriptionErrorCallback = function (error) {
    console.error("WebRtcMediaConnection - onSetRemoteDescriptionErrorCallback(): error: " + error);
};

Configuration = function () {
    this.remoteMediaElementId = null;
    this.localMediaElementId = null;
    this.localMediaElementId2 = null;
    this.videoSourceId = null;
    this.elementIdForSWF = null;
    this.pathToSWF = null;
    this.swfParams = {};
    this.urlWsServer = null;
    this.urlFlashServer = null;
    this.forceFlashForWebRTCBrowser = null;
    this.sipRegisterRequired = true;
    this.sipContactParams = null;

    this.wsPlayerCanvas = null;
    this.wsPlayerReceiverPath = null;
    this.wsPlayerStartWithVideoOnly = false;
    this.wsPlayerKeepLastFrame = false;

    this.videoWidth = 640;
    this.videoHeight = 480;
    this.screenSharingVideoWidth = 1920;
    this.screenSharingVideoHeight = 1080;
    this.screenSharingVideoFps = 10;
    this.forceResolution = false;
    this.audioReliable = false;
    this.videoReliable = false;
    this.flashBufferTime = null;

    // kbps (ex. minBitRate=200 means 200kbps)
    this.minBitRate = null;
    this.maxBitRate = null;

    this.gainControllerEnabled = false;

    this.stunServer = "";

    this.stripCodecs = [];

    this.imdnEnabled = false;
    this.msgContentType = "text/plain";

    this.pushLogEnabled = false;
};

var Connection = function () {
    this.urlServer = undefined;
    this.sipLogin = "";
    this.sipPassword = "";
    this.sipAuthenticationName = "";
    this.sipDomain = "";
    this.sipOutboundProxy = "";
    this.sipPort = 5060;
    this.sipRegisterRequired = true;
    this.appKey = "defaultApp";
    this.status = ConnectionStatus.Pending;
    this.mediaProviders = [];
    this.width = "";
    this.height = "";
};

var RoomStatusEvent = function() {
    this.room = null;
    this.status = null;
    this.login = null;
    this.streamName = null;
    this.time = null;
};

var ConnectionStatus = function () {
};
ConnectionStatus.Pending = "PENDING";
ConnectionStatus.Registered = "REGISTERED";
ConnectionStatus.Established = "ESTABLISHED";
ConnectionStatus.Disconnected = "DISCONNECTED";
ConnectionStatus.Failed = "FAILED";

var RegistrationStatus = function () {
};
RegistrationStatus.Registered = "REGISTERED";
RegistrationStatus.Unregistered = "UNREGISTERED";
RegistrationStatus.Failed = "FAILED";

var Call = function () {
    this.callId = "";
    this.status = "";
    this.caller = "";
    this.callee = "";
    this.incoming = false;
    this.receiveVideo = false;
    this.visibleName = "";
    this.inviteParameters = {};
    this.mediaProvider = undefined;
};

MediaProvider = function () {
};
MediaProvider.WebRTC = "WebRTC";
MediaProvider.Flash = "Flash";
MediaProvider.WSPlayer = "WSPlayer";

var CallStatus = function () {
};
CallStatus.RING = "RING";
CallStatus.RING_MEDIA = "RING_MEDIA";
CallStatus.HOLD = "HOLD";
CallStatus.ESTABLISHED = "ESTABLISHED";
CallStatus.FINISH = "FINISH";
CallStatus.BUSY = "BUSY";
CallStatus.SESSION_PROGRESS = "SESSION_PROGRESS";
CallStatus.FAILED = "FAILED";

var DtmfType = function () {
};

DtmfType.info = "INFO";
DtmfType.info_relay = "INFO_RELAY";
DtmfType.rfc2833 = "RFC2833";

var Message = function () {
    this.from = "";
    this.to = "";
    this.visibleName = undefined;
    this.body = "";
    this.contentType = "";
    this.isImdnRequired = false;
};

var MessageStatus = function () {
};
MessageStatus.SENT = "SENT";
MessageStatus.ACCEPTED = "ACCEPTED";
MessageStatus.FAILED = "FAILED";
MessageStatus.IMDN_NOTIFICATION_SENT = "IMDN_NOTIFICATION_SENT";
MessageStatus.IMDN_DELIVERED = "IMDN_DELIVERED";
MessageStatus.IMDN_FAILED = "IMDN_FAILED";
MessageStatus.IMDN_FORBIDDEN = "IMDN_FORBIDDEN";
MessageStatus.IMDN_ERROR = "IMDN_ERROR";
MessageStatus.RECEIVED = "RECEIVED";

var StreamInfo = function() {
    this.mediaSessionId = null;
    this.name = null;
    this.samplingTime = null;
    this.recordTimestamp = null;
    this.recordStarted = null;
};

var Stream = function () {
    this.mediaSessionId = null;
    this.name = "";
    this.published = false;
    this.hasVideo = false;
    this.status = StreamStatus.Pending;
    this.sdp = "";
    this.info = null;
    this.remoteMediaElementId = null;
    this.record = false;
};

var StreamStatus = function () {
};
StreamStatus.Pending = "PENDING";
StreamStatus.Publishing = "PUBLISHING";
StreamStatus.Playing = "PLAYING";
StreamStatus.Paused = "PAUSED";
StreamStatus.Unpublished = "UNPUBLISHED";
StreamStatus.Stoped = "STOPPED";
StreamStatus.Failed = "FAILED";
StreamStatus.LocalStreamStopped = "LOCAL_STREAM_STOPPED";
StreamStatus.PlaybackProblem = "PLAYBACK_PROBLEM";

var WCSEvent = function () {
};
WCSEvent.ErrorStatusEvent = "ERROR_STATUS_EVENT";
WCSEvent.ConnectionStatusEvent = "CONNECTION_STATUS_EVENT";
WCSEvent.MediaProviderStatusEvent = "MEDIA_PROVIDER_STATUS_EVENT";
WCSEvent.RegistrationStatusEvent = "REGISTRATION_STATUS_EVENT";
WCSEvent.OnCallEvent = "ON_CALL_EVENT";
WCSEvent.CallStatusEvent = "CALL_STATUS_EVENT";
WCSEvent.OnMessageEvent = "ON_MESSAGE_EVENT";
WCSEvent.MessageStatusEvent = "MESSAGE_STATUS_EVENT";
WCSEvent.RecordingStatusEvent = "RECORDING_STATUS_EVENT";
WCSEvent.SubscriptionStatusEvent = "SUBSCRIPTION_STATUS_EVENT";
WCSEvent.StreamStatusEvent = "ON_STREAM_STATUS_EVENT";
WCSEvent.StreamInfoEvent= "ON_STREAM_INFO_EVENT";
WCSEvent.XcapStatusEvent = "XCAP_STATUS_EVENT";
WCSEvent.BugReportStatusEvent = "BUG_REPORT_STATUS_EVENT";
WCSEvent.OnDataEvent = "ON_DATA_EVENT";
WCSEvent.DataStatusEvent = "DATA_STATUS_EVENT";
WCSEvent.TransferStatusEvent = "TRANSFER_STATUS_EVENT";
WCSEvent.OnTransferEvent = "ON_TRANSFER_EVENT";
WCSEvent.OnBinaryEvent = "ON_BINARY_EVENT";
WCSEvent.OnVideoFormatEvent = "ON_VIDEO_FORMAT_EVENT";

var WCSError = function () {
};
WCSError.MIC_ACCESS_PROBLEM = "MIC_ACCESS_PROBLEM";
WCSError.MIC_CAM_ACCESS_PROBLEM = "MIC_CAM_ACCESS_PROBLEM";
WCSError.SCREEN_ACCESS_PROBLEM = "SCREEN_ACCESS_PROBLEM";
WCSError.SCREEN_EXTENSION_UNAVAILABLE = "SCREEN_EXTENSION_UNAVAILABLE";
WCSError.AUTHENTICATION_FAIL = "AUTHENTICATION_FAIL";
WCSError.USER_NOT_AVAILABLE = "USER_NOT_AVAILABLE";
WCSError.TOO_MANY_REGISTER_ATTEMPTS = "TOO_MANY_REGISTER_ATTEMPTS";
WCSError.LICENSE_RESTRICTION = "LICENSE_RESTRICTION";
WCSError.LICENSE_NOT_FOUND = "LICENSE_NOT_FOUND";
WCSError.INTERNAL_SIP_ERROR = "INTERNAL_SIP_ERROR";
WCSError.CONNECTION_ERROR = "CONNECTION_ERROR";
WCSError.REGISTER_EXPIRE = "REGISTER_EXPIRE";
WCSError.SIP_PORTS_BUSY = "SIP_PORTS_BUSY";
WCSError.MEDIA_PORTS_BUSY = "MEDIA_PORTS_BUSY";
WCSError.WRONG_SIPPROVIDER_ADDRESS = "WRONG_SIPPROVIDER_ADDRESS";
WCSError.CALLEE_NAME_IS_NULL = "CALLEE_NAME_IS_NULL";
WCSError.WRONG_FLASHPHONER_XML = "WRONG_FLASHPHONER_XML";
WCSError.PAYMENT_REQUIRED = "PAYMENT_REQUIRED";
WCSError.REST_AUTHORIZATION_FAIL = "REST_AUTHORIZATION_FAIL";
WCSError.REST_FAIL = "REST_FAIL";


var DataMap = function () {
    this.data = {};
};

DataMap.prototype = {

    add: function (id, data) {
        this.data[id] = data;
    },

    update: function (id, data) {
        if (this.get(id)) {
            this.data[id] = data;
        } else {
            console.log("Update failed, key " + id + " doesn't exist");
        }
    },

    get: function (id) {
        return this.data[id];
    },

    remove: function (id) {
        var data = this.data[id];
        delete this.data[id];
        return data;
    },

    getSize: function () {
        return Object.size(this.data);
    },

    getData: function () {
        return this.data;
    },

    array: function () {
        var callArray = [];
        for (var o in this.data) {
            callArray.push(this.data[o]);
        }
        return callArray;
    },

    search: function(key,value) {
        for (var o in this.data) {
            if (this.data[o].hasOwnProperty(key) && this.data[o][key] == value) {
                return this.data[o];
            }
        }
    }
};

function getElement(str) {
    return document.getElementById(str);
}

var isMobile = {
    Android: function () {
        return navigator.userAgent.match(/Android/i);
    },
    BlackBerry: function () {
        return navigator.userAgent.match(/BlackBerry/i);
    },
    iOS: function () {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    },
    Opera: function () {
        return navigator.userAgent.match(/Opera Mini/i);
    },
    Windows: function () {
        return navigator.userAgent.match(/IEMobile/i);
    },
    any: function () {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};

Object.size = function (obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

function escapeXmlTags(stringXml) {
    return stringXml.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function createUUID() {
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
    s[8] = s[13] = s[18] = s[23] = "-";

    var uuid = s.join("");

    return uuid.substring(0, 18);
}


function trace(logMessage) {

    var today = new Date();
    // get hours, minutes and seconds
    var hh = today.getUTCHours().toString();
    var mm = today.getUTCMinutes().toString();
    var ss = today.getUTCSeconds().toString();
    var ms = today.getUTCMilliseconds().toString();

    // Add leading '0' to see 14:08:06.001 instead of 14:8:6.1
    hh = hh.length == 1 ? "0" + hh : hh;
    mm = mm.length == 1 ? "0" + mm : mm;
    ss = ss.length == 1 ? "0" + ss : ss;
    ms = ms.length == 1 ? "00" + ms : ms.length == 2 ? "0" + ms : ms;

    // set time
    var time = "UTC " + hh + ':' + mm + ':' + ss + '.' + ms;

    var logMessage = time + ' - ' + logMessage;

    var console = $("#console");

    if (console.length > 0) {

        // Check if console is scrolled down? Or may be you are reading previous messages.
        var isScrolled = (console[0].scrollHeight - console.height() + 1) / (console[0].scrollTop + 1 + 37);
        console.append(logMessage + '<br>');
    }
    //check if push_log enabled
    if (Flashphoner.getInstance().configuration.pushLogEnabled) {
        var result = Flashphoner.getInstance().pushLogs({logs: logs + logMessage + '\n'});
        if (!result) {
            logs += logMessage + '\n';
        } else {
            logs = "";
        }
    } else {
        logs = "";
    }

    try {
        window.console.debug(logMessage);
    } catch (err) {
        //Not supported. For example IE
    }

    //Autoscroll cosole if you are not reading previous messages
    if (isScrolled < 1) {
        console[0].scrollTop = console[0].scrollHeight;
    }
}

function addLogMessage(message) {
    trace("Flash - " + message);
}

var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;
var webrtcMinimumVersion = null;
var webrtcUtils = {
    log: function () {
        // suppress console.log output when being included as a module.
        if (typeof module !== 'undefined' ||
            typeof require === 'function' && typeof define === 'function') {
            return;
        }
        console.log.apply(console, arguments);
    }
};

if (typeof window === 'object') {
    if (window.HTMLMediaElement && !('srcObject' in window.HTMLMediaElement.prototype)) {
        // Shim the srcObject property, once, when HTMLMediaElement is found.
        Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
            get: function () {
                // If prefixed srcObject property exists, return it.
                // Otherwise use the shimmed property, _srcObject
                return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject;
            },
            set: function (stream) {
                if ('mozSrcObject' in this) {
                    this.mozSrcObject = stream;
                } else {
                    // Use _srcObject as a private property for this shim
                    this._srcObject = stream;
                    // TODO: revokeObjectUrl(this.src) when !stream to release resources?
                    this.src = URL.createObjectURL(stream);
                }
            }
        });
    }
    // Proxy existing globals
    getUserMedia = window.navigator && window.navigator.getUserMedia;
}

// Attach a media stream to an element.
attachMediaStream = function (element, stream) {
    element.srcObject = stream;
};

reattachMediaStream = function (to, from) {
    to.srcObject = from.srcObject;
};

if (typeof window === 'undefined' || !window.navigator) {
    webrtcUtils.log('This does not appear to be a browser');
    webrtcDetectedBrowser = 'not a browser';
} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) {
    webrtcUtils.log('This appears to be Firefox');

    webrtcDetectedBrowser = 'firefox';

    // the detected firefox version.
    webrtcDetectedVersion =
        parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);

    // the minimum firefox version still supported by adapter.
    webrtcMinimumVersion = 31;

    // The RTCPeerConnection object.
    window.RTCPeerConnection = function (pcConfig, pcConstraints) {
        if (webrtcDetectedVersion < 38) {
            // .urls is not supported in FF < 38.
            // create RTCIceServers with a single url.
            if (pcConfig && pcConfig.iceServers) {
                var newIceServers = [];
                for (var i = 0; i < pcConfig.iceServers.length; i++) {
                    var server = pcConfig.iceServers[i];
                    if (server.hasOwnProperty('urls')) {
                        for (var j = 0; j < server.urls.length; j++) {
                            var newServer = {
                                url: server.urls[j]
                            };
                            if (server.urls[j].indexOf('turn') === 0) {
                                newServer.username = server.username;
                                newServer.credential = server.credential;
                            }
                            newIceServers.push(newServer);
                        }
                    } else {
                        newIceServers.push(pcConfig.iceServers[i]);
                    }
                }
                pcConfig.iceServers = newIceServers;
            }
        }
        return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
    };

    // The RTCSessionDescription object.
    if (!window.RTCSessionDescription) {
        window.RTCSessionDescription = mozRTCSessionDescription;
    }

    // The RTCIceCandidate object.
    if (!window.RTCIceCandidate) {
        window.RTCIceCandidate = mozRTCIceCandidate;
    }

    // getUserMedia constraints shim.
    getUserMedia = function (constraints, onSuccess, onError) {
        var constraintsToFF37 = function (c) {
            if (typeof c !== 'object' || c.require) {
                return c;
            }
            var require = [];
            Object.keys(c).forEach(function (key) {
                if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
                    return;
                }
                var r = c[key] = (typeof c[key] === 'object') ?
                    c[key] : {ideal: c[key]};
                if (r.min !== undefined ||
                    r.max !== undefined || r.exact !== undefined) {
                    require.push(key);
                }
                if (r.exact !== undefined) {
                    if (typeof r.exact === 'number') {
                        r.min = r.max = r.exact;
                    } else {
                        c[key] = r.exact;
                    }
                    delete r.exact;
                }
                if (r.ideal !== undefined) {
                    c.advanced = c.advanced || [];
                    var oc = {};
                    if (typeof r.ideal === 'number') {
                        oc[key] = {min: r.ideal, max: r.ideal};
                    } else {
                        oc[key] = r.ideal;
                    }
                    c.advanced.push(oc);
                    delete r.ideal;
                    if (!Object.keys(r).length) {
                        delete c[key];
                    }
                }
            });
            if (require.length) {
                c.require = require;
            }
            return c;
        };
        if (webrtcDetectedVersion < 38) {
            webrtcUtils.log('spec: ' + JSON.stringify(constraints));
            if (constraints.audio) {
                constraints.audio = constraintsToFF37(constraints.audio);
            }
            if (constraints.video) {
                constraints.video = constraintsToFF37(constraints.video);
            }
            webrtcUtils.log('ff37: ' + JSON.stringify(constraints));
        }
        return navigator.mozGetUserMedia(constraints, onSuccess, onError);
    };

    navigator.getUserMedia = getUserMedia;

    // Shim for mediaDevices on older versions.
    if (!navigator.mediaDevices) {
        navigator.mediaDevices = {
            getUserMedia: requestUserMedia,
            addEventListener: function () {
            },
            removeEventListener: function () {
            }
        };
    }
    navigator.mediaDevices.enumerateDevices =
        navigator.mediaDevices.enumerateDevices || function () {
            return new Promise(function (resolve) {
                var infos = [
                    {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
                    {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
                ];
                resolve(infos);
            });
        };

    if (webrtcDetectedVersion < 41) {
        // Work around http://bugzil.la/1169665
        var orgEnumerateDevices =
            navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
        navigator.mediaDevices.enumerateDevices = function () {
            return orgEnumerateDevices().catch(function (e) {
                if (e.name === 'NotFoundError') {
                    return [];
                }
                throw e;
            });
        };
    }
} else if (navigator.webkitGetUserMedia && (!!window.chrome || navigator.userAgent.match(/Opera|OPR\//))) {
    webrtcUtils.log('This appears to be Chrome');

    webrtcDetectedBrowser = 'chrome';

    // the detected chrome version.
    webrtcDetectedVersion =
        parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);

    // the minimum chrome version still supported by adapter.
    webrtcMinimumVersion = 38;

    // The RTCPeerConnection object.
    window.RTCPeerConnection = function (pcConfig, pcConstraints) {
        // Translate iceTransportPolicy to iceTransports,
        // see https://code.google.com/p/webrtc/issues/detail?id=4869
        if (pcConfig && pcConfig.iceTransportPolicy) {
            pcConfig.iceTransports = pcConfig.iceTransportPolicy;
        }

        var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
        var origGetStats = pc.getStats.bind(pc);
        pc.getStats = function (selector, successCallback, errorCallback) { // jshint ignore: line
            var self = this;
            var args = arguments;

            // If selector is a function then we are in the old style stats so just
            // pass back the original getStats format to avoid breaking old users.
            if (arguments.length > 0 && typeof selector === 'function') {
                return origGetStats(selector, successCallback);
            }

            var fixChromeStats = function (response) {
                var standardReport = {};
                var reports = response.result();
                reports.forEach(function (report) {
                    var standardStats = {
                        id: report.id,
                        timestamp: report.timestamp,
                        type: report.type
                    };
                    report.names().forEach(function (name) {
                        standardStats[name] = report.stat(name);
                    });
                    standardReport[standardStats.id] = standardStats;
                });

                return standardReport;
            };

            if (arguments.length >= 2) {
                var successCallbackWrapper = function (response) {
                    args[1](fixChromeStats(response));
                };

                return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
            }

            // promise-support
            return new Promise(function (resolve, reject) {
                if (args.length === 1 && selector === null) {
                    origGetStats.apply(self, [
                        function (response) {
                            resolve.apply(null, [fixChromeStats(response)]);
                        }, reject]);
                } else {
                    origGetStats.apply(self, [resolve, reject]);
                }
            });
        };

        return pc;
    };

    // add promise support
    ['createOffer', 'createAnswer'].forEach(function (method) {
        var nativeMethod = webkitRTCPeerConnection.prototype[method];
        webkitRTCPeerConnection.prototype[method] = function () {
            var self = this;
            if (arguments.length < 1 || (arguments.length === 1 &&
                typeof(arguments[0]) === 'object')) {
                var opts = arguments.length === 1 ? arguments[0] : undefined;
                return new Promise(function (resolve, reject) {
                    nativeMethod.apply(self, [resolve, reject, opts]);
                });
            } else {
                return nativeMethod.apply(this, arguments);
            }
        };
    });

    ['setLocalDescription', 'setRemoteDescription',
        'addIceCandidate'].forEach(function (method) {
        var nativeMethod = webkitRTCPeerConnection.prototype[method];
        webkitRTCPeerConnection.prototype[method] = function () {
            var args = arguments;
            var self = this;
            return new Promise(function (resolve, reject) {
                nativeMethod.apply(self, [args[0],
                    function () {
                        resolve();
                        if (args.length >= 2) {
                            args[1].apply(null, []);
                        }
                    },
                    function (err) {
                        reject(err);
                        if (args.length >= 3) {
                            args[2].apply(null, [err]);
                        }
                    }]
                );
            });
        };
    });

    // getUserMedia constraints shim.
    var constraintsToChrome = function (c) {
        if (typeof c !== 'object' || c.mandatory || c.optional) {
            return c;
        }
        var cc = {};
        Object.keys(c).forEach(function (key) {
            if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
                return;
            }
            var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
            if (r.exact !== undefined && typeof r.exact === 'number') {
                r.min = r.max = r.exact;
            }
            var oldname = function (prefix, name) {
                if (prefix) {
                    return prefix + name.charAt(0).toUpperCase() + name.slice(1);
                }
                return (name === 'deviceId') ? 'sourceId' : name;
            };
            if (r.ideal !== undefined) {
                cc.optional = cc.optional || [];
                var oc = {};
                if (typeof r.ideal === 'number') {
                    oc[oldname('min', key)] = r.ideal;
                    cc.optional.push(oc);
                    oc = {};
                    oc[oldname('max', key)] = r.ideal;
                    cc.optional.push(oc);
                } else {
                    oc[oldname('', key)] = r.ideal;
                    cc.optional.push(oc);
                }
            }
            if (r.exact !== undefined && typeof r.exact !== 'number') {
                cc.mandatory = cc.mandatory || {};
                cc.mandatory[oldname('', key)] = r.exact;
            } else {
                ['min', 'max'].forEach(function (mix) {
                    if (r[mix] !== undefined) {
                        cc.mandatory = cc.mandatory || {};
                        cc.mandatory[oldname(mix, key)] = r[mix];
                    }
                });
            }
        });
        if (c.advanced) {
            cc.optional = (cc.optional || []).concat(c.advanced);
        }
        return cc;
    };

    getUserMedia = function (constraints, onSuccess, onError) {
        if (constraints.audio) {
            constraints.audio = constraintsToChrome(constraints.audio);
        }
        if (constraints.video) {
            constraints.video = constraintsToChrome(constraints.video);
        }
        webrtcUtils.log('chrome: ' + JSON.stringify(constraints));
        return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
    };
    navigator.getUserMedia = getUserMedia;

    if (!navigator.mediaDevices) {
        navigator.mediaDevices = {
            getUserMedia: requestUserMedia,
            enumerateDevices: function () {
                return new Promise(function (resolve) {
                    var kinds = {audio: 'audioinput', video: 'videoinput'};
                    return MediaStreamTrack.getSources(function (devices) {
                        resolve(devices.map(function (device) {
                            return {
                                label: device.label,
                                kind: kinds[device.kind],
                                deviceId: device.id,
                                groupId: ''
                            };
                        }));
                    });
                });
            }
        };
    }

    // A shim for getUserMedia method on the mediaDevices object.
    // TODO(KaptenJansson) remove once implemented in Chrome stable.
    if (!navigator.mediaDevices.getUserMedia) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
            return requestUserMedia(constraints);
        };
    } else {
        // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
        // function which returns a Promise, it does not accept spec-style
        // constraints.
        var origGetUserMedia = navigator.mediaDevices.getUserMedia.
        bind(navigator.mediaDevices);
        navigator.mediaDevices.getUserMedia = function (c) {
            webrtcUtils.log('spec:   ' + JSON.stringify(c)); // whitespace for alignment
            c.audio = constraintsToChrome(c.audio);
            c.video = constraintsToChrome(c.video);
            webrtcUtils.log('chrome: ' + JSON.stringify(c));
            return origGetUserMedia(c);
        };
    }

    // Dummy devicechange event methods.
    // TODO(KaptenJansson) remove once implemented in Chrome stable.
    if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
        navigator.mediaDevices.addEventListener = function () {
            webrtcUtils.log('Dummy mediaDevices.addEventListener called.');
        };
    }
    if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
        navigator.mediaDevices.removeEventListener = function () {
            webrtcUtils.log('Dummy mediaDevices.removeEventListener called.');
        };
    }

    // Attach a media stream to an element.
    attachMediaStream = function (element, stream) {
        if (webrtcDetectedVersion >= 43) {
            element.srcObject = stream;
        } else if (typeof element.src !== 'undefined') {
            element.src = URL.createObjectURL(stream);
        } else {
            webrtcUtils.log('Error attaching stream to element.');
        }
    };
    reattachMediaStream = function (to, from) {
        if (webrtcDetectedVersion >= 43) {
            to.srcObject = from.srcObject;
        } else {
            to.src = from.src;
        }
    };

} else if (navigator.mediaDevices && navigator.userAgent.match(
        /Edge\/(\d+).(\d+)$/)) {
    webrtcUtils.log('This appears to be Edge');
    // Setup FLASH as media provider for Edge
    webrtcDetectedBrowser = null;

    webrtcDetectedVersion =
        parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);

    // the minimum version still supported by adapter.
    webrtcMinimumVersion = 12;
} else {
    webrtcUtils.log('Browser does not appear to be WebRTC-capable');
}

// Returns the result of getUserMedia as a Promise.
function requestUserMedia(constraints) {
    return new Promise(function (resolve, reject) {
        getUserMedia(constraints, resolve, reject);
    });
}

var webrtcTesting = {};
Object.defineProperty(webrtcTesting, 'version', {
    set: function (version) {
        webrtcDetectedVersion = version;
    }
});

if (typeof module !== 'undefined') {
    var RTCPeerConnection;
    if (typeof window !== 'undefined') {
        RTCPeerConnection = window.RTCPeerConnection;
    }
    module.exports = {
        RTCPeerConnection: RTCPeerConnection,
        getUserMedia: getUserMedia,
        attachMediaStream: attachMediaStream,
        reattachMediaStream: reattachMediaStream,
        webrtcDetectedBrowser: webrtcDetectedBrowser,
        webrtcDetectedVersion: webrtcDetectedVersion,
        webrtcMinimumVersion: webrtcMinimumVersion,
        webrtcTesting: webrtcTesting
        //requestUserMedia: not exposed on purpose.
        //trace: not exposed on purpose.
    };
} else if ((typeof require === 'function') && (typeof define === 'function')) {
    // Expose objects and functions when RequireJS is doing the loading.
    define([], function () {
        return {
            RTCPeerConnection: window.RTCPeerConnection,
            getUserMedia: getUserMedia,
            attachMediaStream: attachMediaStream,
            reattachMediaStream: reattachMediaStream,
            webrtcDetectedBrowser: webrtcDetectedBrowser,
            webrtcDetectedVersion: webrtcDetectedVersion,
            webrtcMinimumVersion: webrtcMinimumVersion,
            webrtcTesting: webrtcTesting
            //requestUserMedia: not exposed on purpose.
            //trace: not exposed on purpose.
        };
    });
}

ZeroDay Forums Mini