/* Minification failed. Returning unminified contents.
(1,13): run-time error CSS1031: Expected selector, found '='
(1,13): run-time error CSS1025: Expected comma or open brace, found '='
(7,21): run-time error CSS1031: Expected selector, found '='
(7,21): run-time error CSS1025: Expected comma or open brace, found '='
(9,17): run-time error CSS1031: Expected selector, found '='
(9,17): run-time error CSS1025: Expected comma or open brace, found '='
(10,18): run-time error CSS1031: Expected selector, found '='
(10,18): run-time error CSS1025: Expected comma or open brace, found '='
(12,12): run-time error CSS1031: Expected selector, found ';'
(12,12): run-time error CSS1025: Expected comma or open brace, found ';'
(13,14): run-time error CSS1031: Expected selector, found ';'
(13,14): run-time error CSS1025: Expected comma or open brace, found ';'
(14,15): run-time error CSS1031: Expected selector, found ';'
(14,15): run-time error CSS1025: Expected comma or open brace, found ';'
(15,22): run-time error CSS1031: Expected selector, found ';'
(15,22): run-time error CSS1025: Expected comma or open brace, found ';'
(16,20): run-time error CSS1031: Expected selector, found ';'
(16,20): run-time error CSS1025: Expected comma or open brace, found ';'
(17,17): run-time error CSS1031: Expected selector, found ';'
(17,17): run-time error CSS1025: Expected comma or open brace, found ';'
(18,14): run-time error CSS1031: Expected selector, found ';'
(18,14): run-time error CSS1025: Expected comma or open brace, found ';'
(19,17): run-time error CSS1031: Expected selector, found ';'
(19,17): run-time error CSS1025: Expected comma or open brace, found ';'
(22,16): run-time error CSS1031: Expected selector, found ';'
(22,16): run-time error CSS1025: Expected comma or open brace, found ';'
(23,14): run-time error CSS1031: Expected selector, found ';'
(23,14): run-time error CSS1025: Expected comma or open brace, found ';'
(25,29): run-time error CSS1031: Expected selector, found '='
(25,29): run-time error CSS1025: Expected comma or open brace, found '='
(66,15): run-time error CSS1031: Expected selector, found '='
(66,15): run-time error CSS1025: Expected comma or open brace, found '='
(147,1): run-time error CSS1019: Unexpected token, found 'compositeOfCallbacks('
(149,6): run-time error CSS1030: Expected identifier, found ''onInitPublisher''
(149,6): run-time error CSS1033: Expected closing bracket, found ''onInitPublisher''
(149,25): run-time error CSS1031: Expected selector, found ''onConnect''
(158,1): run-time error CSS1019: Unexpected token, found ')'
(160,10): run-time error CSS1030: Expected identifier, found 'addEventListener('
(160,10): run-time error CSS1031: Expected selector, found 'addEventListener('
(160,10): run-time error CSS1025: Expected comma or open brace, found 'addEventListener('
(184,2): run-time error CSS1019: Unexpected token, found ')'
(188,10): run-time error CSS1031: Expected selector, found 'setText('
(188,10): run-time error CSS1025: Expected comma or open brace, found 'setText('
(192,10): run-time error CSS1031: Expected selector, found 'setButtons('
(192,10): run-time error CSS1025: Expected comma or open brace, found 'setButtons('
(204,10): run-time error CSS1031: Expected selector, found 'pluck('
(204,10): run-time error CSS1025: Expected comma or open brace, found 'pluck('
(210,10): run-time error CSS1031: Expected selector, found 'sum('
(210,10): run-time error CSS1025: Expected comma or open brace, found 'sum('
(220,10): run-time error CSS1031: Expected selector, found 'max('
(220,10): run-time error CSS1025: Expected comma or open brace, found 'max('
(224,10): run-time error CSS1031: Expected selector, found 'min('
(224,10): run-time error CSS1025: Expected comma or open brace, found 'min('
(228,10): run-time error CSS1031: Expected selector, found 'calculatePerSecondStats('
(228,10): run-time error CSS1025: Expected comma or open brace, found 'calculatePerSecondStats('
(250,10): run-time error CSS1031: Expected selector, found 'getSampleWindowSize('
(250,10): run-time error CSS1025: Expected comma or open brace, found 'getSampleWindowSize('
(255,4): run-time error CSS1031: Expected selector, found '('
(255,4): run-time error CSS1025: Expected comma or open brace, found '('
(263,10): run-time error CSS1031: Expected selector, found 'compositeOfCallbacks('
(263,10): run-time error CSS1025: Expected comma or open brace, found 'compositeOfCallbacks('
(293,10): run-time error CSS1031: Expected selector, found 'bandwidthCalculatorObj('
(293,10): run-time error CSS1025: Expected comma or open brace, found 'bandwidthCalculatorObj('
(358,10): run-time error CSS1031: Expected selector, found 'performQualityTest('
(358,10): run-time error CSS1025: Expected comma or open brace, found 'performQualityTest('
(390,10): run-time error CSS1031: Expected selector, found 'createAudioMeter('
(390,10): run-time error CSS1025: Expected comma or open brace, found 'createAudioMeter('
(422,10): run-time error CSS1031: Expected selector, found 'volumeAudioProcess('
(422,10): run-time error CSS1025: Expected comma or open brace, found 'volumeAudioProcess('
(447,28): run-time error CSS1031: Expected selector, found '='
(447,28): run-time error CSS1025: Expected comma or open brace, found '='
(685,11): run-time error CSS1031: Expected selector, found '='
(685,11): run-time error CSS1025: Expected comma or open brace, found '='
(687,4): run-time error CSS1030: Expected identifier, found 'applyBindings('
(687,4): run-time error CSS1031: Expected selector, found 'applyBindings('
(687,4): run-time error CSS1025: Expected comma or open brace, found 'applyBindings('
(689,1): run-time error CSS1019: Unexpected token, found '$'
(689,2): run-time error CSS1019: Unexpected token, found '('
(689,3): run-time error CSS1019: Unexpected token, found '"#systemCheckContainer"'
(689,26): run-time error CSS1019: Unexpected token, found ')'
(689,28): run-time error CSS1030: Expected identifier, found 'removeClass('
(689,28): run-time error CSS1019: Unexpected token, found 'removeClass('
(689,40): run-time error CSS1019: Unexpected token, found '"invisible"'
(689,51): run-time error CSS1019: Unexpected token, found ')'
 */
let _config = {
    apiKey: "",
    sessionId: "",
    token: ""
};

let TEST_TIMEOUT_MS = 10000; // 15 seconds

let publisherEl = document.createElement('div');
let subscriberEl = document.createElement('div');

let session;
let publisher;
let subscriber;
let statusContainerEl;
let statusMessageEl;
let statusIconEl;
let viewModel;
let timeoutCount;
//let homeButton;
//let retryButton;
let videoStream;
let micStream;

let testStreamingCapability = function (subscriber, callback) {
    performQualityTest({ subscriber: subscriber, timeout: TEST_TIMEOUT_MS }, function (error, results) {
        console.log('Test concluded', results);
        // If we tried to set video constraints, but no video data was found
        if (!results.video) {
            return callback(false, {
                text: 'No camera was found ',
                icon: '/Assets/Images/SpeedTest/icon_warning.svg',
                isPassed: true
            });
        }

        let audioVideoSupported = results.video.bitsPerSecond > 25000 &&
            results.video.packetLossRatioPerSecond < 0.03 &&
            results.audio.bitsPerSecond > 25000 &&
            results.audio.packetLossRatioPerSecond < 0.05;

        if (audioVideoSupported) {
            return callback(false, {
                text: 'Awesome! Looks like you\'re all set!',
                icon: '/Assets/Images/SpeedTest/icon_tick.svg',
                isPassed: true
            });
        }

        if (results.audio.packetLossRatioPerSecond < 0.05) {
            return callback(false, {
                text: 'Your bandwidth is too low for a video call',
                icon: '/Assets/Images/SpeedTest/icon_warning.svg',
                isPassed: true
            });
        }

        return callback(false, {
            text: 'Your bandwidth is too low for a video call or an audio call',
            icon: '/Assets/Images/SpeedTest/icon_error.svg',
            isPassed: false
        });
    });
};

let callbacks = {
    onInitPublisher: function onInitPublisher(error) {
        if (error) {
            setText('Could not acquire your camera');

            return;
        }

        setText('Connecting to session');
    },

    onPublish: function onPublish(error) {
        if (error) {
            // handle publishing errors here
            setText('Could not publish video');
            //setButtons(false, homeButton, retryButton)
            return;
        }

        setText('Subscribing to video');

        subscriber = session.subscribe(
            publisher.stream,
            subscriberEl,
            {
                audioVolume: 0,
                testNetwork: true
            },
            callbacks.onSubscribe
        );
    },

    cleanup: function () {
        session.unsubscribe(subscriber);
        session.unpublish(publisher);
    },

    onSubscribe: function onSubscribe(error, subscriber) {
        if (error) {
            setText('Could not subscribe to video');
            //setButtons(false, homeButton, retryButton)
            return;
        }

        setText('Checking your available bandwidth');

        testStreamingCapability(subscriber, function (error, message) {
            setText(message.text);

            viewModel.check[4].loading(false);
            viewModel.check[4].showBtn(false);

            if (message.isPassed) {
                viewModel.check[4].success(true);
                viewModel.passed(true);
            } else {
                viewModel.check[4].fail(true);
            }

            callbacks.cleanup();
        });
    },

    onConnect: function onConnect(error) {
        if (error) {
            setText('Could not connect to Server');

            $.post("/portal/dashboard/Confirm-Compatibility-Check", {}, function (result) {
                if (result.Succeeded === true) {
                    viewModel.check[4].loading(false);
                    viewModel.check[4].showBtn(false);
                    viewModel.check[4].fail(true);
                }
                else if (result.Errors) {
                    showErrorMessage(result.Errors);
                }
            });
        }
    }
};

compositeOfCallbacks(
    callbacks,
    ['onInitPublisher', 'onConnect'],
    function (error) {
        if (error) {
            return;
        }

        setText('Transmitting video');
        session.publish(publisher, callbacks.onPublish);
    }
);

document.addEventListener('DOMContentLoaded', function () {
    let container = document.createElement('div');
    container.className = 'container';

    container.appendChild(publisherEl);
    container.appendChild(subscriberEl);
    document.body.appendChild(container);

    // This publisher uses the default resolution (640x480 pixels) and frame rate (30fps).
    // For other resoultions you may need to adjust the bandwidth conditions in
    // testStreamingCapability().

    //publisher = OT.initPublisher(publisherEl, {}, callbacks.onInitPublisher);

    //session = OT.initSession(_config.apiKey, _config.sessionId);
    //session.connect(_config.token, callbacks.onConnect);


    //statusContainerEl = document.getElementById('status_container');
    //statusMessageEl = statusContainerEl.querySelector('p');
    //statusIconEl = statusContainerEl.querySelector('img');
    //homeButton = document.getElementById('home_button');
    //retryButton = document.getElementById('retry_button');

});

// Helpers

function setText(text) {
    viewModel.check[4].message(text);
}

function setButtons(status, homeButton, retryButton) {
    if (!homeButton && !retryButton) {
        return;
    }
    if (status) {
        homeButton.classList.remove('hidden')
    } else {
        retryButton.classList.remove('hidden')
    }

}

function pluck(arr, propertName) {
    return arr.map(function (value) {
        return value[propertName];
    });
}

function sum(arr, propertyName) {
    if (typeof propertyName !== 'undefined') {
        arr = pluck(arr, propertyName);
    }

    return arr.reduce(function (previous, current) {
        return previous + current;
    }, 0);
}

function max(arr) {
    return Math.max.apply(undefined, arr);
}

function min(arr) {
    return Math.min.apply(undefined, arr);
}

function calculatePerSecondStats(statsBuffer, seconds) {
    let stats = {};
    let activeMediaTypes = Object.keys(statsBuffer[0] || {})
        .filter(function (key) {
            return key !== 'timestamp';
        });

    activeMediaTypes.forEach(function (type) {
        stats[type] = {
            packetsPerSecond: sum(pluck(statsBuffer, type), 'packetsReceived') / seconds,
            bitsPerSecond: (sum(pluck(statsBuffer, type), 'bytesReceived') * 8) / seconds,
            packetsLostPerSecond: sum(pluck(statsBuffer, type), 'packetsLost') / seconds
        };
        stats[type].packetLossRatioPerSecond = (
            stats[type].packetsLostPerSecond / stats[type].packetsPerSecond
        );
    });

    stats.windowSize = seconds;
    return stats;
}

function getSampleWindowSize(samples) {
    let times = pluck(samples, 'timestamp');
    return (max(times) - min(times)) / 1000;
}

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (fn, scope) {
        for (let i = 0, len = this.length; i < len; ++i) {
            fn.call(scope, this[i], i, this);
        }
    };
}

function compositeOfCallbacks(obj, fns, callback) {
    let results = {};
    let hasError = false;

    let checkDone = function checkDone() {
        if (Object.keys(results).length === fns.length) {
            callback(hasError, results);
            callback = function () { };
        }
    };

    fns.forEach(function (key) {
        let originalCallback = obj[key];

        obj[key] = function (error) {
            results[key] = {
                error: error,
                args: Array.prototype.slice.call(arguments, 1)
            };

            if (error) {
                hasError = true;
            }

            originalCallback.apply(obj, arguments);
            checkDone();
        };
    });
}

function bandwidthCalculatorObj(config) {
    let intervalId;

    config.pollingInterval = config.pollingInterval || 500;
    config.windowSize = config.windowSize || 2000;
    config.subscriber = config.subscriber || undefined;

    return {
        start: function (reportFunction) {
            let statsBuffer = [];
            let last = {
                audio: {},
                video: {}
            };
            let activeMediaTypes = subscriber.stream.channel
                .map(function (channel) {
                    if (channel.active === true) {
                        return channel.type;
                    }
                })
                .filter(function (mediaType) {
                    return !!mediaType;
                });


            intervalId = window.setInterval(function () {
                config.subscriber.getStats(function (error, stats) {
                    let snapshot = {};
                    let nowMs = new Date().getTime();
                    let sampleWindowSize;

                    activeMediaTypes.forEach(function (type) {
                        snapshot[type] = Object.keys(stats[type]).reduce(function (result, key) {
                            result[key] = stats[type][key] - (last[type][key] || 0);
                            last[type][key] = stats[type][key];
                            return result;
                        }, {});
                    });

                    // get a snapshot of now, and keep the last values for next round
                    snapshot.timestamp = stats.timestamp;

                    statsBuffer.push(snapshot);
                    statsBuffer = statsBuffer.filter(function (value) {
                        return nowMs - value.timestamp < config.windowSize;
                    });

                    sampleWindowSize = getSampleWindowSize(statsBuffer);

                    if (sampleWindowSize !== 0) {
                        reportFunction(calculatePerSecondStats(
                            statsBuffer,
                            sampleWindowSize + (config.pollingInterval / 1000)
                        ));
                    }
                });
            }, config.pollingInterval);
        },

        stop: function () {
            window.clearInterval(intervalId);
        }
    };
}

function performQualityTest(config, callback) {
    let startMs = new Date().getTime();
    let testTimeout;
    let currentStats;

    let bandwidthCalculator = bandwidthCalculatorObj({
        subscriber: config.subscriber
    });

    let cleanupAndReport = function () {
        currentStats.elapsedTimeMs = new Date().getTime() - startMs;
        callback(undefined, currentStats);

        window.clearTimeout(testTimeout);
        bandwidthCalculator.stop();

        callback = function () { };
    };

    // bail out of the test after 30 seconds
    window.setTimeout(cleanupAndReport, config.timeout);

    bandwidthCalculator.start(function (stats) {
        //console.log(stats);

        // you could do something smart here like determine if the bandwidth is
        // stable or acceptable and exit early
        currentStats = stats;
    });
}

// Audio meter functions
function createAudioMeter(audioContext, clipLevel, averaging, clipLag) {
    let processor = audioContext.createScriptProcessor(512);
    processor.onaudioprocess = volumeAudioProcess;
    processor.clipping = false;
    processor.lastClip = 0;
    processor.volume = 0;
    processor.clipLevel = clipLevel || 0.98;
    processor.averaging = averaging || 0.95;
    processor.clipLag = clipLag || 750;

    // this will have no effect, since we don't copy the input to the output,
    // but works around a current Chrome bug.
    processor.connect(audioContext.destination);

    processor.checkClipping =
        function () {
            if (!this.clipping)
                return false;
            if ((this.lastClip + this.clipLag) < window.performance.now())
                this.clipping = false;
            return this.clipping;
        };

    processor.shutdown =
        function () {
            this.disconnect();
            this.onaudioprocess = null;
        };

    return processor;
}

function volumeAudioProcess(event) {
    let buf = event.inputBuffer.getChannelData(0);
    let bufLength = buf.length;
    let sum = 0;
    let x;

    // Do a root-mean-square on the samples: sum up the squares...
    for (let i = 0; i < bufLength; i++) {
        x = buf[i];
        if (Math.abs(x) >= this.clipLevel) {
            this.clipping = true;
            this.lastClip = window.performance.now();
        }
        sum += x * x;
    }

    // ... then take the square root of the sum.
    let rms = Math.sqrt(sum / bufLength);

    // Now smooth this out with the averaging factor applied
    // to the previous sample - take the max here because we
    // want "fast attack, slow release."
    this.volume = Math.max(rms, this.volume * this.averaging);
}

let CompatibilityViewModel = function () {
    let self = this;
    self.check = [
        {
            id: 1,
            logo: "/Assets/Images/SpeedTest/system-check.png",
            title: "General System Check",
            optionalDesc: "Welcome! Let's run a System Check!",
            desc: "Let's make sure you are all setup correctly...",
            btnDesc: "Start System Check",
            loading: ko.observable(false),
            showConfirm: ko.observable(false),
            showBtn: ko.observable(true),
            success: ko.observable(false),
            fail: ko.observable(false),
            failDesc: "",
            message: ko.observable("")
        }, {
            id: 2,
            logo: "/Assets/Images/SpeedTest/camera-check.png",
            title: "Camera Check",
            optionalDesc: "",
            desc: "Excellent! Now let's make sure your camera can be used for making video calls...",
            btnDesc: "Check Video",
            loading: ko.observable(false),
            showConfirm: ko.observable(false),
            showBtn: ko.observable(false),
            success: ko.observable(false),
            fail: ko.observable(false),
            failDesc: "Hmm.... Looks like we can't seem to activate your camera. Please make sure you have given permission for your browser to access your camera and try again. Perhaps also check that it has been installed correctly if you're using an external camera to connect to your computer. You may need to seek technical assistance if you're unable to resolve this.",
            message: ko.observable("")
        }, {
            id: 3,
            logo: "/Assets/Images/SpeedTest/microphone-check.png",
            title: "Microphone Check",
            optionalDesc: "",
            desc: "You look great! How about your microphone? Perhaps you can say something after you click the button below when you're ready?",
            btnDesc: "I'm Ready",
            loading: ko.observable(false),
            showConfirm: ko.observable(false),
            showBtn: ko.observable(false),
            success: ko.observable(false),
            fail: ko.observable(false),
            failDesc: "Hmm... Sounds like we can't seem to hear you. Please make sure you have given permission for your browser to access your microphone and try again? Perhaps also check your microphone volume to see if it is set too low or on mute. You may need to seek technical assistance if you're unable to resolve this.",
            message: ko.observable("")
        }, {
            id: 4,
            logo: "/Assets/Images/SpeedTest/speaker-check.png",
            title: "Speaker Check",
            optionalDesc: "",
            desc: "That sounds good! But can you hear us?",
            btnDesc: "Let's Find Out",
            loading: ko.observable(false),
            showConfirm: ko.observable(false),
            showBtn: ko.observable(false),
            success: ko.observable(false),
            fail: ko.observable(false),
            failDesc: "Hmm... We don't think you can hear us. Please make sure you have given permission for your browser to access your speakers and try again? Perhaps also check your speaker volume to see if it is set too low or on mute. You may need to seek technical assistance if you're unable to resolve this.",
            message: ko.observable("")
        }, {
            id: 5,
            logo: "/Assets/Images/SpeedTest/connection-speed-check.png",
            title: "Connection Speed Test",
            optionalDesc: "",
            desc: "We're so glad you can hear us! Now, let's make sure your internet connection is fast enough to make video calls...",
            btnDesc: "Start Speed Test",
            loading: ko.observable(false),
            showConfirm: ko.observable(false),
            showBtn: ko.observable(false),
            success: ko.observable(false),
            fail: ko.observable(false),
            failDesc: "Oh no! Unfortunately the speed and bandwidth of your current internet connection is not suitable for MedicRelief. Please connect to another internet connection and run the Speed Test later to make sure it has the capacity for video calls.",
            message: ko.observable("")
        }
    ];

    self.passed = ko.observable(false);

    self.startChecking = function (c) {
        if (c.id < 6) {
            c.showConfirm(false);
            c.success(false);
            c.fail(false);

            c.loading(true);
            c.showBtn(false);
        }

        if (c.id === 1) {
            let isChrome = true; // Assume back-end already check the browser support, just add the loading feeling of it.
            if (isChrome) {
                setTimeout(function () {
                    c.loading(false);
                    c.success(true);
                    self.check[c.id++].showBtn(true);
                }, 2000);
            } else {
                setTimeout(function () {
                    c.loading(false);
                    c.fail(true);
                    self.check[c.id++].showBtn(true);
                }, 2000);
            }
        } else if (c.id === 2) {
            let video = document.querySelector("#videoCheck");

            if (navigator.mediaDevices.getUserMedia) {
                navigator.mediaDevices.getUserMedia({ video: true })
                    .then(function (stream) {
                        videoStream = stream;

                        c.showConfirm(true);
                        video.srcObject = stream;
                    })
                    .catch(function (err0r) {
                        console.log("Something went wrong! - video");
                        c.loading(false);
                        c.fail(true);
                    });
            } else {
                console.log("Something went wrong! - video 2");
                c.loading(false);
                c.fail(true);
            }
        } else if (c.id === 3) {
            if (navigator.mediaDevices.getUserMedia) {
                navigator.mediaDevices.getUserMedia({ audio: true })
                    .then(function (stream) {
                        micStream = stream;

                        c.showConfirm(true);

                        let AudioContext = window.AudioContext // Default
                            || window.webkitAudioContext // Safari and old versions of Chrome
                            || false;

                        let audioContext = new AudioContext();
                        let meter = null;
                        let canvasContext = document.getElementById("microphoneCheck").getContext("2d");
                        let WIDTH = 500;
                        let HEIGHT = 50;
                        let rafID = null;
                        let mediaStreamSource = null;

                        // Create an AudioNode from the stream.
                        mediaStreamSource = audioContext.createMediaStreamSource(stream);

                        // Create a new volume meter and connect it.
                        meter = createAudioMeter(audioContext);
                        mediaStreamSource.connect(meter);

                        // kick off the visual updating
                        drawLoop();

                        function drawLoop(time) {
                            // clear the background
                            canvasContext.clearRect(0, 0, WIDTH, HEIGHT);

                            // check if we're currently clipping
                            if (meter.checkClipping())
                                canvasContext.fillStyle = "red";
                            else
                                canvasContext.fillStyle = "green";

                            // draw a bar based on the current volume
                            canvasContext.fillRect(0, 0, meter.volume * WIDTH * 1.4, HEIGHT);

                            // set up the next visual callback
                            rafID = window.requestAnimationFrame(drawLoop);
                        }
                    }).catch(function (err0r) {
                        console.log(err0r);
                        console.log("Something went wrong! - Mic1");
                        c.loading(false);
                        c.fail(true);
                    });
            } else {
                console.log("Something went wrong! - Mic2");
                c.loading(false);
                c.fail(true);
            }
        } else if (c.id === 4) {
            let x = document.getElementById("speakerCheck");
            x.play();

            c.showConfirm(true);

            timeoutCount = setTimeout(function () {
                c.loading(false);
                c.fail(true);
            }, 30000);
        } else if (c.id === 5) {
            publisher = OT.initPublisher(publisherEl, {}, callbacks.onInitPublisher);
            session = OT.initSession(_config.apiKey, _config.sessionId);
            session.connect(_config.token, callbacks.onConnect);
        }
    };

    self.confirmChecking = function (c) {
        clearTimeout(timeoutCount);

        c.loading(false);
        c.success(true);

        stopMedia(c.id);

        self.check[c.id++].showBtn(true);
    };

    self.confirmNo = function (c) {
        clearTimeout(timeoutCount);

        stopMedia(c.id);

        c.loading(false);
        c.fail(true);
    };

    self.skip = function (c) {
        c.loading(false);
        c.success(true);
        c.fail(false);

        self.check[c.id++].showBtn(true);
    };

    let stopMedia = function (id) {
        let track;
        if (id == 2) {
            track = videoStream.getTracks()[0];
            track.stop();
        } else if (id == 3) {
            track = micStream.getTracks()[0];
            track.stop();
        }
    };
};

viewModel = new CompatibilityViewModel();

ko.applyBindings(viewModel);

$("#systemCheckContainer").removeClass("invisible");
