diff --git a/Canvas-Designer/Gruntfile.js b/Canvas-Designer/Gruntfile.js index 6e4a3f97..39d61a4a 100755 --- a/Canvas-Designer/Gruntfile.js +++ b/Canvas-Designer/Gruntfile.js @@ -7,6 +7,8 @@ module.exports = function(grunt) { scope: 'devDependencies' }); + var banner = '// Last time updated: <%= grunt.template.today("UTC:yyyy-mm-dd h:MM:ss TT Z") %>\n\n'; + // configure project grunt.initConfig({ // make node configurations available @@ -15,7 +17,7 @@ module.exports = function(grunt) { options: { stripBanners: true, separator: '\n', - banner: '// Last time updated at <%= grunt.template.today("dddd, mmmm dS, yyyy, h:MM:ss TT") %> \n\n' + banner: banner }, dist: { src: [ @@ -32,7 +34,7 @@ module.exports = function(grunt) { 'dev/line-handler.js', 'dev/rect-handler.js', 'dev/quadratic-handler.js', - 'bezier-handler.js', + 'dev/bezier-handler.js', 'dev/file-selector.js', 'dev/image-handler.js', 'dev/events-handler.js', @@ -48,7 +50,7 @@ module.exports = function(grunt) { uglify: { options: { mangle: false, - banner: '// Last time updated at <%= grunt.template.today("dddd, mmmm dS, yyyy, h:MM:ss TT") %> \n\n' + banner: banner }, my_target: { files: { diff --git a/Canvas-Designer/README.md b/Canvas-Designer/README.md index 0553413f..ff0c1c20 100644 --- a/Canvas-Designer/README.md +++ b/Canvas-Designer/README.md @@ -70,14 +70,14 @@ E.g. (Please don't forget replacing `1.0.0` with latest version) ```html - + diff --git a/Canvas-Designer/canvas-designer-widget.js b/Canvas-Designer/canvas-designer-widget.js index 85ee16f0..c46e6305 100644 --- a/Canvas-Designer/canvas-designer-widget.js +++ b/Canvas-Designer/canvas-designer-widget.js @@ -39,8 +39,6 @@ var CanvasDesigner = (function() { } } - window.addEventListener('message', onMessage, false); - return { appendTo: function(parentNode) { iframe = document.createElement('iframe'); @@ -49,6 +47,9 @@ var CanvasDesigner = (function() { iframe.style.height = '100%'; iframe.style.border = 0; parentNode.appendChild(iframe); + + window.removeEventListener('message', onMessage); + window.addEventListener('message', onMessage, false); }, destroy: function() { if(iframe) { diff --git a/Canvas-Designer/dev/arc-handler.js b/Canvas-Designer/dev/arc-handler.js index 881cf625..6ab4d584 100644 --- a/Canvas-Designer/dev/arc-handler.js +++ b/Canvas-Designer/dev/arc-handler.js @@ -1,9 +1,4 @@ -// ------------------------------------------------------------- - var arcHandler = { - - // ------------------------------------------------------------- - global: { ismousedown: false, prevX: 0, @@ -15,9 +10,6 @@ var arcHandler = { arcRangeContainer: null, arcRange: null }, - - // ------------------------------------------------------------- - mousedown: function(e) { var g = this.global; @@ -29,9 +21,6 @@ var arcHandler = { g.ismousedown = true; }, - - // ------------------------------------------------------------- - mouseup: function(e) { var g = this.global; @@ -75,9 +64,6 @@ var arcHandler = { this.fixAllPoints(); }, - - // ------------------------------------------------------------- - mousemove: function(e) { var g = this.global; @@ -100,9 +86,6 @@ var arcHandler = { } } }, - - // ------------------------------------------------------------- - fixAllPoints: function() { var toFixed = this.toFixed; @@ -117,9 +100,6 @@ var arcHandler = { } } }, - - // ------------------------------------------------------------- - init: function() { var markIsClockwise = find('is-clockwise'), g = this.global; @@ -148,9 +128,6 @@ var arcHandler = { addEvent(arcRange, 'keydown', this.arcRangeHandler); addEvent(arcRange, 'focus', this.arcRangeHandler); }, - - // ------------------------------------------------------------- - arcRangeHandler: function(e) { var g = arcHandler.global, arcRange = g.arcRange; @@ -174,15 +151,9 @@ var arcHandler = { } } }, - - // ------------------------------------------------------------- - toFixed: function(input) { return Number(input).toFixed(1); }, - - // ------------------------------------------------------------- - end: function() { var g = this.global; @@ -194,10 +165,6 @@ var arcHandler = { drawHelper.redraw(); } - - // ------------------------------------------------------------- }; arcHandler.init(); - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/bezier-handler.js b/Canvas-Designer/dev/bezier-handler.js index c81dbaa2..f910c610 100644 --- a/Canvas-Designer/dev/bezier-handler.js +++ b/Canvas-Designer/dev/bezier-handler.js @@ -1,9 +1,4 @@ -// ------------------------------------------------------------- - var bezierHandler = { - - // ------------------------------------------------------------- - global: { ismousedown: false, prevX: 0, @@ -18,9 +13,6 @@ var bezierHandler = { isSecondStep: false, isLastStep: false }, - - // ------------------------------------------------------------- - mousedown: function(e) { var g = this.global; @@ -46,9 +38,6 @@ var bezierHandler = { g.isLastStep = true; } }, - - // ------------------------------------------------------------- - mouseup: function(e) { var g = this.global; @@ -63,9 +52,6 @@ var bezierHandler = { g.isSecondStep = true; } }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -86,9 +72,6 @@ var bezierHandler = { drawHelper.bezier(tempContext, [g.prevX, g.prevY, g.firstControlPointX, g.firstControlPointY, g.secondControlPointX, g.secondControlPointY, x, y]); } }, - - // ------------------------------------------------------------- - end: function(x, y) { var g = this.global; @@ -107,9 +90,4 @@ var bezierHandler = { points[points.length] = ['bezier', [g.prevX, g.prevY, g.firstControlPointX, g.firstControlPointY, g.secondControlPointX, g.secondControlPointY, x, y], drawHelper.getOptions()]; } - - // ------------------------------------------------------------- - }; - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/common.js b/Canvas-Designer/dev/common.js index db1fe7c4..76769f02 100644 --- a/Canvas-Designer/dev/common.js +++ b/Canvas-Designer/dev/common.js @@ -1,4 +1,3 @@ -// ------------------------------------------------------------- var is = { isLine: false, isArc: false, @@ -20,8 +19,6 @@ var is = { } }; -// ------------------------------------------------------------- - function addEvent(element, eventType, callback) { if (element.addEventListener) { element.addEventListener(eventType, callback, !1); @@ -31,14 +28,10 @@ function addEvent(element, eventType, callback) { return this; } -// ------------------------------------------------------------- - function find(selector) { return document.getElementById(selector); } -// ------------------------------------------------------------- - var points = [], textarea = find('code-text'), lineWidth = 2, @@ -50,8 +43,6 @@ var points = [], font = '15px Verdana', lineJoin = 'miter'; -// ------------------------------------------------------------- - function getContext(id) { var canv = find(id), ctx = canv.getContext('2d'); @@ -67,18 +58,10 @@ function getContext(id) { return ctx; } -// ------------------------------------------------------------- - var context = getContext('main-canvas'), tempContext = getContext('temp-canvas'); - -// ------------------------------------------------------------- - var common = { - - // ------------------------------------------------------------- - updateTextArea: function() { var c = common, toFixed = c.toFixed, @@ -92,15 +75,9 @@ var common = { if (!isAbsolutePoints && isShortenCode) c.relativeShortened(toFixed, getPoint); if (!isAbsolutePoints && !isShortenCode) c.relativeNOTShortened(toFixed, getPoint); }, - - // ------------------------------------------------------------- - toFixed: function(input) { return Number(input).toFixed(1); }, - - // ------------------------------------------------------------- - getPoint: function(pointToCompare, compareWith, prefix) { if (pointToCompare > compareWith) pointToCompare = prefix + ' + ' + (pointToCompare - compareWith); else if (pointToCompare < compareWith) pointToCompare = prefix + ' - ' + (compareWith - pointToCompare); @@ -108,13 +85,7 @@ var common = { return pointToCompare; }, - - - - // ------------------------------------------------------------- - absoluteShortened: function() { - var output = '', length = points.length, i = 0, @@ -129,9 +100,6 @@ var common = { this.prevProps = null; }, - - // ------------------------------------------------------------- - absoluteNOTShortened: function(toFixed) { var tempArray = [], i, point, p; @@ -177,9 +145,6 @@ var common = { this.prevProps = null; }, - - // ------------------------------------------------------------- - relativeShortened: function(toFixed, getPoint) { var i = 0, point, p, length = points.length, @@ -285,9 +250,6 @@ var common = { this.prevProps = null; }, - - // ------------------------------------------------------------- - relativeNOTShortened: function(toFixed, getPoint) { var i, point, p, length = points.length, output = '', @@ -360,69 +322,60 @@ var common = { this.prevProps = null; }, + forLoop: 'for(i; i < length; i++) {\n' + ' p = points[i];\n' + ' point = p[1];\n' + ' context.beginPath();\n\n' - // ------------------------------------------------------------- + // globals + + ' if(p[2]) { \n' + ' context.lineWidth = p[2][0];\n' + ' context.strokeStyle = p[2][1];\n' + ' context.fillStyle = p[2][2];\n' - forLoop: 'for(i; i < length; i++) {\n' + '\t p = points[i];\n' + '\t point = p[1];\n' + '\t context.beginPath();\n\n' + + ' context.globalAlpha = p[2][3];\n' + ' context.globalCompositeOperation = p[2][4];\n' + ' context.lineCap = p[2][5];\n' + ' context.lineJoin = p[2][6];\n' + ' context.font = p[2][7];\n' + ' }\n\n' - // ------------------------------------------------------------- - + '\t if(p[2]) { \n' + '\t\t context.lineWidth = p[2][0];\n' + '\t\t context.strokeStyle = p[2][1];\n' + '\t\t context.fillStyle = p[2][2];\n' + // line - + '\t\t context.globalAlpha = p[2][3];\n' + '\t\t context.globalCompositeOperation = p[2][4];\n' + '\t\t context.lineCap = p[2][5];\n' + '\t\t context.lineJoin = p[2][6];\n' + '\t\t context.font = p[2][7];\n' + '\t }\n\n' + + ' if(p[0] === "line") { \n' + ' context.moveTo(point[0], point[1]);\n' + ' context.lineTo(point[2], point[3]);\n' + ' }\n\n' - // ------------------------------------------------------------- + // pencil - + '\t if(p[0] === "line") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + + ' if(p[0] === "pencil") { \n' + ' context.moveTo(point[0], point[1]);\n' + ' context.lineTo(point[2], point[3]);\n' + ' }\n\n' - // ------------------------------------------------------------- + // text - + '\t if(p[0] === "pencil") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + + ' if(p[0] === "text") { \n' + ' context.fillText(point[0], point[1], point[2]);\n' + ' }\n\n' - // ------------------------------------------------------------- + // eraser - + '\t if(p[0] === "text") { \n' + '\t\t context.fillText(point[0], point[1], point[2]);\n' + '\t }\n\n' + + ' if(p[0] === "eraser") { \n' + ' context.moveTo(point[0], point[1]);\n' + ' context.lineTo(point[2], point[3]);\n' + ' }\n\n' + // arc - // ------------------------------------------------------------- + + ' if(p[0] === "arc") context.arc(point[0], point[1], point[2], point[3], 0, point[4]); \n\n' - + '\t if(p[0] === "eraser") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + // rect - // ------------------------------------------------------------- + + ' if(p[0] === "rect") {\n' + ' context.strokeRect(point[0], point[1], point[2], point[3]);\n' + ' context.fillRect(point[0], point[1], point[2], point[3]);\n' - + '\t if(p[0] === "arc") context.arc(point[0], point[1], point[2], point[3], 0, point[4]); \n\n' + + ' }\n\n' - // ------------------------------------------------------------- + // quadratic - + '\t if(p[0] === "rect") {\n' + '\t\t context.strokeRect(point[0], point[1], point[2], point[3]);\n' + '\t\t context.fillRect(point[0], point[1], point[2], point[3]);\n' + + ' if(p[0] === "quadratic") {\n' + ' context.moveTo(point[0], point[1]);\n' + ' context.quadraticCurveTo(point[2], point[3], point[4], point[5]);\n' + ' }\n\n' - + '\t }\n\n' + // bezier - // ------------------------------------------------------------- + + ' if(p[0] === "bezier") {\n' + ' context.moveTo(point[0], point[1]);\n' + ' context.bezierCurveTo(point[2], point[3], point[4], point[5], point[6], point[7]);\n' + ' }\n\n' - + '\t if(p[0] === "quadratic") {\n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.quadraticCurveTo(point[2], point[3], point[4], point[5]);\n' + '\t }\n\n' + // end-fill - // ------------------------------------------------------------- - - + '\t if(p[0] === "bezier") {\n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.bezierCurveTo(point[2], point[3], point[4], point[5], point[6], point[7]);\n' + '\t }\n\n' - - // ------------------------------------------------------------- - - + '\t context.stroke();\n' + '\t context.fill();\n' + + ' context.stroke();\n' + ' context.fill();\n' + '}', - // ------------------------------------------------------------- + strokeFillText: '\n\nfunction strokeOrFill(lineWidth, strokeStyle, fillStyle, globalAlpha, globalCompositeOperation, lineCap, lineJoin, font) { \n' + ' if(lineWidth) { \n' + ' context.globalAlpha = globalAlpha;\n' + ' context.globalCompositeOperation = globalCompositeOperation;\n' + ' context.lineCap = lineCap;\n' + ' context.lineJoin = lineJoin;\n' - strokeFillText: '\n\nfunction strokeOrFill(lineWidth, strokeStyle, fillStyle, globalAlpha, globalCompositeOperation, lineCap, lineJoin, font) { \n' + '\t if(lineWidth) { \n' + '\t\t context.globalAlpha = globalAlpha;\n' + '\t\t context.globalCompositeOperation = globalCompositeOperation;\n' + '\t\t context.lineCap = lineCap;\n' + '\t\t context.lineJoin = lineJoin;\n' + + ' context.lineWidth = lineWidth;\n' + ' context.strokeStyle = strokeStyle;\n' + ' context.fillStyle = fillStyle;\n' + ' context.font = font;\n' + ' } \n\n' - + '\t\t context.lineWidth = lineWidth;\n' + '\t\t context.strokeStyle = strokeStyle;\n' + '\t\t context.fillStyle = fillStyle;\n' + '\t\t context.font = font;\n' + '\t } \n\n' - - + '\t context.stroke();\n' + '\t context.fill();\n' + + ' context.stroke();\n' + ' context.fill();\n' + '}', - - // ------------------------------------------------------------- - strokeOrFill: function(p) { if (!this.prevProps || this.prevProps !== p.join(',')) { this.prevProps = p.join(','); @@ -432,8 +385,6 @@ var common = { return 'strokeOrFill();'; }, - - // ------------------------------------------------------------- prevProps: null, shortenHelper: function(name, p1, p2) { var result = '["' + name + '", [' + p1.join(', ') + ']'; @@ -445,13 +396,8 @@ var common = { return result + '], '; } - - // ------------------------------------------------------------- - }; -// ------------------------------------------------------------- - function endLastPath() { var cache = is; @@ -462,13 +408,9 @@ function endLastPath() { drawHelper.redraw(); } -// ------------------------------------------------------------- - var copiedStuff = [], isControlKeyPressed; -// ------------------------------------------------------------- - function copy() { endLastPath(); @@ -483,8 +425,6 @@ function copy() { } } -// ------------------------------------------------------------- - function paste() { endLastPath(); @@ -508,5 +448,3 @@ function paste() { setSelection(find('drag-all-paths'), 'DragAllPaths'); } } - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/decorator.js b/Canvas-Designer/dev/decorator.js index 3c9d8315..b6b88092 100644 --- a/Canvas-Designer/dev/decorator.js +++ b/Canvas-Designer/dev/decorator.js @@ -1,4 +1,3 @@ -// ------------------------------------------------------------- var tools = { line: true, pencil: true, @@ -9,7 +8,8 @@ var tools = { arc: true, bezier: true, quadratic: true, - text: true + text: true, + image: true }; if (params.tools) { @@ -28,8 +28,6 @@ function setSelection(element, prop) { element.className += ' selected-shape'; } -// ------------------------------------------------------------- - (function() { var cache = {}; @@ -37,8 +35,6 @@ function setSelection(element, prop) { var lineCapSelect = find('lineCap-select'); var lineJoinSelect = find('lineJoin-select'); - // ------------------------------------------------------------- - function getContext(id) { var context = find(id).getContext('2d'); context.lineWidth = 2; @@ -46,8 +42,6 @@ function setSelection(element, prop) { return context; } - // ------------------------------------------------------------- - function bindEvent(context, shape) { if (shape === 'Pencil') { lineCap = lineJoin = 'round'; @@ -62,6 +56,25 @@ function setSelection(element, prop) { } else is.set('Pencil'); addEvent(context.canvas, 'click', function() { + if (textHandler.text.length) { + points[points.length] = ['text', ['"' + textHandler.text + '"', textHandler.x, textHandler.y], drawHelper.getOptions()]; + } + + if (shape === 'Text') { + tempContext.canvas.style.cursor = 'text'; + textHandler.x = textHandler.y = 0; + textHandler.text = ''; + } else { + textHandler.text = ''; + tempContext.canvas.style.cursor = 'default'; + if (typeof textHandler.blinkCursorInterval !== 'undefined') { + clearInterval(textHandler.blinkCursorInterval); + } + } + + if (shape === 'Pencil') { + lineCap = lineJoin = 'round'; + } dragHelper.global.startingIndex = 0; @@ -123,14 +136,9 @@ function setSelection(element, prop) { }); } - // ------------------------------------------------------------- - var toolBox = find('tool-box'); toolBox.style.height = (innerHeight /* - toolBox.offsetTop - 77 */ ) + 'px'; - // ------------------------------------------------------------- - - function decorateDragLastPath() { var context = getContext('drag-last-path'); @@ -171,8 +179,6 @@ function setSelection(element, prop) { decorateDragLastPath(); } else document.getElementById('drag-last-path').style.display = 'none'; - // ------------------------------------------------------------- - function decorateDragAllPaths() { var context = getContext('drag-all-paths'); @@ -213,8 +219,6 @@ function setSelection(element, prop) { decorateDragAllPaths(); } else document.getElementById('drag-all-paths').style.display = 'none'; - // ------------------------------------------------------------- - function decorateLine() { var context = getContext('line'); @@ -233,8 +237,6 @@ function setSelection(element, prop) { decorateLine(); } else document.getElementById('line').style.display = 'none'; - // ------------------------------------------------------------- - function decoratePencil() { var context = getContext('pencil-icon'); @@ -255,8 +257,6 @@ function setSelection(element, prop) { decoratePencil(); } else document.getElementById('pencil-icon').style.display = 'none'; - // ------------------------------------------------------------- - function decorateEraser() { var context = getContext('eraser-icon'); @@ -277,8 +277,6 @@ function setSelection(element, prop) { decorateEraser(); } else document.getElementById('eraser-icon').style.display = 'none'; - // ------------------------------------------------------------- - function decorateText() { var context = getContext('text-icon'); @@ -292,8 +290,6 @@ function setSelection(element, prop) { decorateText(); } else document.getElementById('text-icon').style.display = 'none'; - // ------------------------------------------------------------- - function decorateImage() { var context = getContext('image-icon'); @@ -309,8 +305,6 @@ function setSelection(element, prop) { decorateImage(); } else document.getElementById('image-icon').style.display = 'none'; - // ------------------------------------------------------------- - function decorateArc() { var context = getContext('arc'); @@ -328,8 +322,6 @@ function setSelection(element, prop) { decorateArc(); } else document.getElementById('arc').style.display = 'none'; - // ------------------------------------------------------------- - function decorateRect() { var context = getContext('rectangle'); @@ -346,8 +338,6 @@ function setSelection(element, prop) { decorateRect(); } else document.getElementById('rectangle').style.display = 'none'; - // ------------------------------------------------------------- - function decorateQuadratic() { var context = getContext('quadratic-curve'); @@ -366,8 +356,6 @@ function setSelection(element, prop) { decorateQuadratic(); } else document.getElementById('quadratic-curve').style.display = 'none'; - // ------------------------------------------------------------- - function decorateBezier() { var context = getContext('bezier-curve'); @@ -390,8 +378,6 @@ function setSelection(element, prop) { decorateBezier(); } else document.getElementById('bezier-curve').style.display = 'none'; - // ------------------------------------------------------------- - function tempStrokeTheLine(context, width, mx, my, lx, ly) { context.beginPath(); context.lineWidth = width; @@ -436,7 +422,6 @@ function setSelection(element, prop) { decorateLineWidth(); - // ------------------------------------------------------------- function decorateColors() { var context = getContext('colors'); @@ -475,7 +460,6 @@ function setSelection(element, prop) { decorateColors(); - // ------------------------------------------------------------- function decorateAdditionalOptions() { var context = getContext('additional'); @@ -514,13 +498,9 @@ function setSelection(element, prop) { decorateAdditionalOptions(); - // ------------------------------------------------------------- - var designPreview = find('design-preview'), codePreview = find('code-preview'); - // ------------------------------------------------------------- - // todo: use this function in share-drawings.js // to sync buttons' states window.selectBtn = function(btn, isSkipWebRTCMessage) { @@ -540,8 +520,6 @@ function setSelection(element, prop) { } }; - // ------------------------------------------------------------- - addEvent(designPreview, 'click', function() { selectBtn(designPreview); btnDesignerPreviewClicked(); @@ -555,8 +533,6 @@ function setSelection(element, prop) { endLastPath(); } - // ------------------------------------------------------------- - addEvent(codePreview, 'click', function() { selectBtn(codePreview); btnCodePreviewClicked(); @@ -575,13 +551,9 @@ function setSelection(element, prop) { endLastPath(); } - // ------------------------------------------------------------- - var codeText = find('code-text'), optionsContainer = find('options-container'); - // ------------------------------------------------------------- - function setHeightForCodeAndOptionsContainer() { codeText.style.width = (innerWidth - optionsContainer.clientWidth - 30) + 'px'; codeText.style.height = (innerHeight - 40) + 'px'; @@ -590,20 +562,14 @@ function setSelection(element, prop) { optionsContainer.style.height = (innerHeight) + 'px'; } - // ------------------------------------------------------------- - var isAbsolute = find('is-absolute-points'), isShorten = find('is-shorten-code'); addEvent(isShorten, 'change', common.updateTextArea); addEvent(isAbsolute, 'change', common.updateTextArea); - // ------------------------------------------------------------- - })(); -// ------------------------------------------------------------- - function hideContainers() { var additionalContainer = find('additional-container'), colorsContainer = find('colors-container'), @@ -611,5 +577,3 @@ function hideContainers() { additionalContainer.style.display = colorsContainer.style.display = lineWidthContainer.style.display = 'none'; } - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/drag-helper.js b/Canvas-Designer/dev/drag-helper.js index 25d82ebf..f559cfe4 100644 --- a/Canvas-Designer/dev/drag-helper.js +++ b/Canvas-Designer/dev/drag-helper.js @@ -1,8 +1,4 @@ -// ------------------------------------------------------------- var dragHelper = { - - // ------------------------------------------------------------- - global: { prevX: 0, prevY: 0, @@ -10,21 +6,13 @@ var dragHelper = { pointsToMove: 'all', startingIndex: 0 }, - - // ------------------------------------------------------------- - mousedown: function(e) { - - // ------------------------------------------------------------- - if (isControlKeyPressed) { copy(); paste(); isControlKeyPressed = false; } - // ------------------------------------------------------------- - var dHelper = dragHelper, g = dHelper.global; @@ -126,9 +114,6 @@ var dragHelper = { g.ismousedown = true; }, - - // ------------------------------------------------------------- - mouseup: function() { var g = this.global; @@ -140,9 +125,6 @@ var dragHelper = { g.ismousedown = false; }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop, @@ -156,9 +138,6 @@ var dragHelper = { if (is.isDragLastPath) this.init(); }, - - // ------------------------------------------------------------- - init: function() { if (!points.length) return; @@ -244,15 +223,9 @@ var dragHelper = { tempContext.fill(); } }, - - // ------------------------------------------------------------- - isPointInPath: function(x, y, first, second) { return x > first - 10 && x < first + 10 && y > second - 10 && y < second + 10; }, - - // ------------------------------------------------------------- - getPoint: function(point, prev, otherPoint) { if (point > prev) { point = otherPoint + (point - prev); @@ -262,9 +235,6 @@ var dragHelper = { return point; }, - - // ------------------------------------------------------------- - getXYWidthHeight: function(x, y, prevX, prevY, oldPoints) { if (oldPoints.pointsToMove == 'stretch-first') { if (x > prevX) { @@ -318,9 +288,6 @@ var dragHelper = { return oldPoints; }, - - // ------------------------------------------------------------- - dragShape: function(x, y) { if (!this.global.ismousedown) return; @@ -339,9 +306,6 @@ var dragHelper = { g.prevX = x; g.prevY = y; }, - - // ------------------------------------------------------------- - end: function() { if (!points.length) return; @@ -350,9 +314,6 @@ var dragHelper = { var point = points[points.length - 1]; drawHelper[point[0]](context, point[1], point[2]); }, - - // ------------------------------------------------------------- - dragAllPaths: function(x, y) { var g = this.global, prevX = g.prevX, @@ -453,9 +414,6 @@ var dragHelper = { } } }, - - // ------------------------------------------------------------- - dragLastPath: function(x, y) { var g = this.global, prevX = g.prevX, @@ -659,8 +617,4 @@ var dragHelper = { points[points.length - 1] = [p[0], point, p[2]]; } } - - // ------------------------------------------------------------- - }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/draw-helper.js b/Canvas-Designer/dev/draw-helper.js index 9cad3501..ace61073 100644 --- a/Canvas-Designer/dev/draw-helper.js +++ b/Canvas-Designer/dev/draw-helper.js @@ -1,8 +1,4 @@ -// ------------------------------------------------------------- var drawHelper = { - - // ------------------------------------------------------------- - redraw: function(skipSync) { tempContext.clearRect(0, 0, innerWidth, innerHeight); context.clearRect(0, 0, innerWidth, innerHeight); @@ -17,15 +13,9 @@ var drawHelper = { syncPoints(); } }, - - // ------------------------------------------------------------- - getOptions: function() { return [lineWidth, strokeStyle, fillStyle, globalAlpha, globalCompositeOperation, lineCap, lineJoin, font]; }, - - // ------------------------------------------------------------- - handleOptions: function(context, opt, isNoFillStroke) { opt = opt || this.getOptions(); @@ -44,9 +34,6 @@ var drawHelper = { context.fill(); } }, - - // ------------------------------------------------------------- - line: function(context, point, options) { context.beginPath(); context.moveTo(point[0], point[1]); @@ -54,9 +41,6 @@ var drawHelper = { this.handleOptions(context, options); }, - - // ------------------------------------------------------------- - text: function(context, point, options) { var oldFillStyle = fillStyle; context.fillStyle = fillStyle === 'transparent' || fillStyle === 'White' ? 'Black' : fillStyle; @@ -66,27 +50,18 @@ var drawHelper = { this.handleOptions(context, options); }, - - // ------------------------------------------------------------- - arc: function(context, point, options) { context.beginPath(); context.arc(point[0], point[1], point[2], point[3], 0, point[4]); this.handleOptions(context, options); }, - - // ------------------------------------------------------------- - rect: function(context, point, options) { this.handleOptions(context, options, true); context.strokeRect(point[0], point[1], point[2], point[3]); context.fillRect(point[0], point[1], point[2], point[3]); }, - - // ------------------------------------------------------------- - image: function(context, point, options) { this.handleOptions(context, options, true); @@ -108,9 +83,6 @@ var drawHelper = { context.drawImage(image, point[1], point[2], point[3], point[4]); }, - - // ------------------------------------------------------------- - quadratic: function(context, point, options) { context.beginPath(); context.moveTo(point[0], point[1]); @@ -118,9 +90,6 @@ var drawHelper = { this.handleOptions(context, options); }, - - // ------------------------------------------------------------- - bezier: function(context, point, options) { context.beginPath(); context.moveTo(point[0], point[1]); @@ -128,7 +97,4 @@ var drawHelper = { this.handleOptions(context, options); } - - // ------------------------------------------------------------- }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/eraser-handler.js b/Canvas-Designer/dev/eraser-handler.js index c8337573..83a3cb52 100644 --- a/Canvas-Designer/dev/eraser-handler.js +++ b/Canvas-Designer/dev/eraser-handler.js @@ -1,15 +1,7 @@ -// ------------------------------------------------------------- - var eraserHandler = { - - // ------------------------------------------------------------- - ismousedown: false, prevX: 0, prevY: 0, - - // ------------------------------------------------------------- - mousedown: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -21,8 +13,6 @@ var eraserHandler = { t.ismousedown = true; - // make sure that pencil is drawing shapes even - // if mouse is down but mouse isn't moving tempContext.lineCap = 'round'; drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); @@ -31,15 +21,9 @@ var eraserHandler = { t.prevX = x; t.prevY = y; }, - - // ------------------------------------------------------------- - mouseup: function(e) { this.ismousedown = false; }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -56,9 +40,4 @@ var eraserHandler = { t.prevY = y; } } - - // ------------------------------------------------------------- - }; - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/events-handler.js b/Canvas-Designer/dev/events-handler.js index 68787c06..540e3133 100644 --- a/Canvas-Designer/dev/events-handler.js +++ b/Canvas-Designer/dev/events-handler.js @@ -1,10 +1,6 @@ -// ------------------------------------------------------------- - var canvas = tempContext.canvas, isTouch = 'createTouch' in document; -// ------------------------------------------------------------- - addEvent(canvas, isTouch ? 'touchstart' : 'mousedown', function(e) { if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { pageX: 0, @@ -27,8 +23,6 @@ addEvent(canvas, isTouch ? 'touchstart' : 'mousedown', function(e) { drawHelper.redraw(); }); -// ------------------------------------------------------------- - addEvent(canvas, isTouch ? 'touchend' : 'mouseup', function(e) { if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { pageX: 0, @@ -51,8 +45,6 @@ addEvent(canvas, isTouch ? 'touchend' : 'mouseup', function(e) { drawHelper.redraw(); }); -// ------------------------------------------------------------- - addEvent(canvas, isTouch ? 'touchmove' : 'mousemove', function(e) { if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { pageX: 0, @@ -73,14 +65,17 @@ addEvent(canvas, isTouch ? 'touchmove' : 'mousemove', function(e) { else if (cache.isImage) imageHandler.mousemove(e); }); -// ------------------------------------------------------------- - var keyCode; -// ------------------------------------------------------------- - function onkeydown(e) { - keyCode = e.keyCode; + keyCode = e.which || e.keyCode || 0; + + if (keyCode == 8 || keyCode == 46) { + if (isBackKey(e, keyCode)) { + // back key pressed + } + return; + } if (e.metaKey) { isControlKeyPressed = true; @@ -92,9 +87,32 @@ function onkeydown(e) { } } -addEvent(document, 'keydown', onkeydown); +function isBackKey(e, keyCode) { + var doPrevent = false; + var d = e.srcElement || e.target; + if ((d.tagName.toUpperCase() === 'INPUT' && + ( + d.type.toUpperCase() === 'TEXT' || + d.type.toUpperCase() === 'PASSWORD' || + d.type.toUpperCase() === 'FILE' || + d.type.toUpperCase() === 'SEARCH' || + d.type.toUpperCase() === 'EMAIL' || + d.type.toUpperCase() === 'NUMBER' || + d.type.toUpperCase() === 'DATE') + ) || + d.tagName.toUpperCase() === 'TEXTAREA') { + doPrevent = d.readOnly || d.disabled; + } else { + doPrevent = true; + } + + if (doPrevent) { + e.preventDefault(); + } + return doPrevent; +} -// ------------------------------------------------------------- +addEvent(document, 'keydown', onkeydown); function onkeyup(e) { if (e.which == null && (e.charCode != null || e.keyCode != null)) { @@ -103,8 +121,14 @@ function onkeyup(e) { keyCode = e.which || e.keyCode || 0; - /*-------------------------- Ctrl + Z --------------------------*/ + if (keyCode == 8 || keyCode == 46) { + if (isBackKey(e, keyCode)) { + textHandler.writeText(textHandler.lastKeyPress, true); + } + return; + } + // Ctrl + Z if (isControlKeyPressed && keyCode === 90) { if (points.length) { points.length = points.length - 1; @@ -112,8 +136,7 @@ function onkeyup(e) { } } - /*-------------------------- Ctrl + A --------------------------*/ - + // Ctrl + A if (isControlKeyPressed && keyCode === 65) { dragHelper.global.startingIndex = 0; @@ -122,19 +145,17 @@ function onkeyup(e) { setSelection(find('drag-all-paths'), 'DragAllPaths'); } - /*-------------------------- Ctrl + C --------------------------*/ - + // Ctrl + C if (isControlKeyPressed && keyCode === 67 && points.length) { copy(); } - /*-------------------------- Ctrl + V --------------------------*/ + // Ctrl + V if (isControlKeyPressed && keyCode === 86 && copiedStuff.length) { paste(); } - /*-------------------------- Ending the Control Key --------------------------*/ - + // Ending the Control Key if (typeof e.metaKey !== 'undefined' && e.metaKey === false) { isControlKeyPressed = false; keyCode = 17; @@ -147,4 +168,17 @@ function onkeyup(e) { addEvent(document, 'keyup', onkeyup); -// ------------------------------------------------------------- +function onkeypress(e) { + if (e.which == null && (e.charCode != null || e.keyCode != null)) { + e.which = e.charCode != null ? e.charCode : e.keyCode; + } + + keyCode = e.which || e.keyCode || 0; + + var inp = String.fromCharCode(keyCode); + if (/[a-zA-Z0-9-_ !?|\/'",.=:;(){}\[\]`~@#$%^&*+-]/.test(inp)) { + textHandler.writeText(String.fromCharCode(keyCode)); + } +} + +addEvent(document, 'keypress', onkeypress); diff --git a/Canvas-Designer/dev/image-handler.js b/Canvas-Designer/dev/image-handler.js index 2a7f27ed..ed33b9b1 100644 --- a/Canvas-Designer/dev/image-handler.js +++ b/Canvas-Designer/dev/image-handler.js @@ -1,8 +1,4 @@ -// ------------------------------------------------------------- var imageHandler = { - - // ------------------------------------------------------------- - lastImageURL: null, lastImageIndex: 0, images: [], @@ -10,9 +6,6 @@ var imageHandler = { ismousedown: false, prevX: 0, prevY: 0, - - // ------------------------------------------------------------- - mousedown: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -24,9 +17,6 @@ var imageHandler = { t.ismousedown = true; }, - - // ------------------------------------------------------------- - mouseup: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -39,9 +29,6 @@ var imageHandler = { } }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -53,8 +40,4 @@ var imageHandler = { drawHelper.image(tempContext, [imageHandler.lastImageURL, t.prevX, t.prevY, x - t.prevX, y - t.prevY, imageHandler.lastImageIndex]); } } - - // ------------------------------------------------------------- - }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/line-handler.js b/Canvas-Designer/dev/line-handler.js index 37b81e81..9856aff5 100644 --- a/Canvas-Designer/dev/line-handler.js +++ b/Canvas-Designer/dev/line-handler.js @@ -1,15 +1,7 @@ -// ------------------------------------------------------------- - var lineHandler = { - - // ------------------------------------------------------------- - ismousedown: false, prevX: 0, prevY: 0, - - // ------------------------------------------------------------- - mousedown: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -21,9 +13,6 @@ var lineHandler = { t.ismousedown = true; }, - - // ------------------------------------------------------------- - mouseup: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -35,9 +24,6 @@ var lineHandler = { t.ismousedown = false; } }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -50,8 +36,4 @@ var lineHandler = { drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); } } - - // ------------------------------------------------------------- - }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/pencil-handler.js b/Canvas-Designer/dev/pencil-handler.js index c2c8ab17..e47fa0e9 100644 --- a/Canvas-Designer/dev/pencil-handler.js +++ b/Canvas-Designer/dev/pencil-handler.js @@ -1,15 +1,7 @@ -// ------------------------------------------------------------- - var pencilHandler = { - - // ------------------------------------------------------------- - ismousedown: false, prevX: 0, prevY: 0, - - // ------------------------------------------------------------- - mousedown: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -31,15 +23,9 @@ var pencilHandler = { t.prevX = x; t.prevY = y; }, - - // ------------------------------------------------------------- - mouseup: function(e) { this.ismousedown = false; }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -56,9 +42,4 @@ var pencilHandler = { t.prevY = y; } } - - // ------------------------------------------------------------- - }; - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/quadratic-handler.js b/Canvas-Designer/dev/quadratic-handler.js index 0291459c..547beaa7 100644 --- a/Canvas-Designer/dev/quadratic-handler.js +++ b/Canvas-Designer/dev/quadratic-handler.js @@ -1,9 +1,4 @@ -// ------------------------------------------------------------- - var quadraticHandler = { - - // ------------------------------------------------------------- - global: { ismousedown: false, prevX: 0, @@ -13,9 +8,6 @@ var quadraticHandler = { isFirstStep: true, isLastStep: false }, - - // ------------------------------------------------------------- - mousedown: function(e) { var g = this.global; @@ -33,9 +25,6 @@ var quadraticHandler = { this.end(x, y); } }, - - // ------------------------------------------------------------- - mouseup: function(e) { var g = this.global; @@ -50,9 +39,6 @@ var quadraticHandler = { g.isLastStep = true; } }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -69,9 +55,6 @@ var quadraticHandler = { drawHelper.quadratic(tempContext, [g.prevX, g.prevY, g.controlPointX, g.controlPointY, x, y]); } }, - - // ------------------------------------------------------------- - end: function(x, y) { var g = this.global; @@ -87,9 +70,4 @@ var quadraticHandler = { points[points.length] = ['quadratic', [g.prevX, g.prevY, g.controlPointX, g.controlPointY, x, y], drawHelper.getOptions()]; } - - // ------------------------------------------------------------- - }; - -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/rect-handler.js b/Canvas-Designer/dev/rect-handler.js index f5fa9674..585293a1 100644 --- a/Canvas-Designer/dev/rect-handler.js +++ b/Canvas-Designer/dev/rect-handler.js @@ -1,14 +1,7 @@ -// ------------------------------------------------------------- var rectHandler = { - - // ------------------------------------------------------------- - ismousedown: false, prevX: 0, prevY: 0, - - // ------------------------------------------------------------- - mousedown: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -20,9 +13,6 @@ var rectHandler = { t.ismousedown = true; }, - - // ------------------------------------------------------------- - mouseup: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -35,9 +25,6 @@ var rectHandler = { } }, - - // ------------------------------------------------------------- - mousemove: function(e) { var x = e.pageX - canvas.offsetLeft, y = e.pageY - canvas.offsetTop; @@ -49,8 +36,4 @@ var rectHandler = { drawHelper.rect(tempContext, [t.prevX, t.prevY, x - t.prevX, y - t.prevY]); } } - - // ------------------------------------------------------------- - }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/dev/share-drawings.js b/Canvas-Designer/dev/share-drawings.js index 03934ac4..80c9c261 100644 --- a/Canvas-Designer/dev/share-drawings.js +++ b/Canvas-Designer/dev/share-drawings.js @@ -1,8 +1,6 @@ -// ------------------------------------------------------------- // scripts on this page directly touches DOM-elements // removing or altering anything may cause failures in the UI event handlers // it is used only to bring collaboration for canvas-surface -// ------------------------------------------------------------- var lastPoint = []; var selfId = (Math.random() * 10000).toString().replace('.', ''); diff --git a/Canvas-Designer/dev/text-handler.js b/Canvas-Designer/dev/text-handler.js index fa6af96b..64e94483 100644 --- a/Canvas-Designer/dev/text-handler.js +++ b/Canvas-Designer/dev/text-handler.js @@ -1,78 +1,53 @@ -// ------------------------------------------------------------- -var textInput = document.getElementById('text-input'); -textInput.onkeyup = function(e) { - if (e.keyCode != 13) return; - - // ENTER key goes to new line - fillText(); - - textHandler.isTextPending = true; - - textHandler.y += 20; - textHandler.pageY += 20; - - textInput.style.top = (textHandler.pageY - 10) + 'px'; - textInput.style.left = (textHandler.pageX - 10) + 'px'; - textInput.style.color = fillStyle == 'transparent' ? 'Black' : fillStyle; - - setTimeout(function() { - textInput.focus(); - }, 200); -}; - -textInput.onblur = function(e) { - if (textInput.value.length) { - fillText(); - return; - } - //textInput.style.top = '-100000px'; - //textInput.style.left = '-100000px'; - //textHandler.isTextPending = false; -}; - -function fillText() { - if (!textHandler.isTextPending) return; - textHandler.isTextPending = false; - - var oldFillStyle = fillStyle; - var oldFont = font; - - fillStyle = 'Black'; - font = '15px Verdana'; - - points[points.length] = ['text', ['"' + textInput.value + '"', textHandler.x, textHandler.y], drawHelper.getOptions()]; - - fillStyle = oldFillStyle; - font = oldFont; - - textInput.style.top = '-100000px'; - textInput.style.left = '-100000px'; - textInput.value = ''; - - drawHelper.redraw(); -} - var textHandler = { - isTextPending: false, + text: '', + writeText: function(keyPressed, isBackKeyPressed) { + if (isBackKeyPressed) { + textHandler.text = textHandler.text.substr(0, textHandler.text.length - 1); + textHandler.fillText(textHandler.text); + return; + } + + textHandler.text += keyPressed; + textHandler.fillText(textHandler.text); + }, + fillText: function(text) { + tempContext.clearRect(0, 0, tempContext.canvas.width, tempContext.canvas.height); + + tempContext.fillStyle = 'black'; + tempContext.font = font; + tempContext.fillText(text, textHandler.x, textHandler.y); + }, + blinkCursorInterval: null, + index: 0, + blinkCursor: function() { + textHandler.index++; + if (textHandler.index % 2 == 0) { + textHandler.fillText(textHandler.text + '|'); + } else { + textHandler.fillText(textHandler.text); + } + }, mousedown: function(e) { - if (textHandler.isTextPending) fillText(); - textHandler.isTextPending = true; + if (textHandler.text.length) { + points[points.length] = ['text', ['"' + textHandler.text + '"', textHandler.x, textHandler.y], drawHelper.getOptions()]; + } + + textHandler.x = textHandler.y = 0; + textHandler.text = ''; textHandler.pageX = e.pageX; textHandler.pageY = e.pageY; - textHandler.x = e.pageX - canvas.offsetLeft - 10; - textHandler.y = e.pageY - canvas.offsetTop + 5; + textHandler.x = e.pageX - canvas.offsetLeft - 5; + textHandler.y = e.pageY - canvas.offsetTop + 10; - textInput.style.top = (e.pageY - 10) + 'px'; - textInput.style.left = (e.pageX - 10) + 'px'; - textInput.style.color = fillStyle == 'transparent' ? 'Black' : fillStyle; + if (typeof textHandler.blinkCursorInterval !== 'undefined') { + clearInterval(textHandler.blinkCursorInterval); + } - setTimeout(function() { - textInput.focus(); - }, 200); + textHandler.blinkCursor(); + textHandler.blinkCursorInterval = setInterval(textHandler.blinkCursor, 700); }, mouseup: function(e) {}, mousemove: function(e) {} }; -// ------------------------------------------------------------- diff --git a/Canvas-Designer/package.json b/Canvas-Designer/package.json index 4a01b666..75c3c35d 100644 --- a/Canvas-Designer/package.json +++ b/Canvas-Designer/package.json @@ -1,7 +1,7 @@ { "name": "canvas-designer", "preferGlobal": false, - "version": "1.0.0", + "version": "1.0.2", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", diff --git a/Canvas-Designer/widget.html b/Canvas-Designer/widget.html index 20fa39fd..23ac5937 100644 --- a/Canvas-Designer/widget.html +++ b/Canvas-Designer/widget.html @@ -12,8 +12,7 @@ +
+ + + -
- +
+
+
+ - +

Share Files privately & securely

+
+
+
-
-
0
-
+
0
+
+

+ Peer-to-Peer (private) file sharing. +

+

+ You can share/receive files from any platform/device e.g. destkop operating systems, Android, iOS etc. +

+

+ Create or join a room & select file using "+" button. +

+
+ - - - - + + + + + + - + \ No newline at end of file diff --git a/Chrome-Extensions/file-sharing/file-sharing.js b/Chrome-Extensions/file-sharing/file-sharing.js index 2005e080..11cac6e7 100755 --- a/Chrome-Extensions/file-sharing/file-sharing.js +++ b/Chrome-Extensions/file-sharing/file-sharing.js @@ -1,19 +1,24 @@ // Muaz Khan - https://github.com/muaz-khan // MIT License - https://www.WebRTC-Experiment.com/licence/ -// Source Code - https://github.com/muaz-khan/Chrome-Extensions -// Muaz Khan - https://github.com/muaz-khan +// Source Code - https://github.com/muaz-khan/RTCMultiConnection var iframe = document.querySelector('iframe'); + var btnSelectFile = document.querySelector('.btn-select-file'); + btnSelectFile.onclick = function() { var fileSelector = new FileSelector(); fileSelector.selectSingleFile(function(file) { previewFile(file); + onFileSelected(file); }); }; + var connection; var lastSelectedFile; + var room_id = ''; + // 60k -- assuming receiving client is chrome var chunk_size = 60 * 1000; @@ -21,93 +26,158 @@ function setupWebRTCConnection() { if (connection) { return; } + // www.RTCMultiConnection.org/docs/ connection = new RTCMultiConnection(); - // connection.autoReDialOnFailure = false // to make sure, "connection-reconnect" doesn't sends files again connection.fileReceived = {}; + + // by default, socket.io server is assumed to be deployed on your own URL + // connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; - connection.socketMessageEvent = 'file-sharing'; + + connection.socketMessageEvent = 'file-sharing-demo'; + connection.chunkSize = chunk_size; + connection.sdpConstraints.mandatory = { OfferToReceiveAudio: false, OfferToReceiveVideo: false }; + connection.enableFileSharing = true; + if (room_id && room_id.length) { connection.userid = room_id; } - var resultingURL = 'https://rtcmulticonnection.herokuapp.com/demos/file-sharing.html#' + connection.userid; - if (location.href.indexOf('rtcmulticonnection.herokuapp.com') !== -1) { - // connection.userid = (Math.random() * 100).toString().replace('.', ''); - resultingURL = location.href; + if(!room_id) { + room_id = connection.token(); } - connection.channel = connection.sessionid = resultingURL.split('#').pop(); + connection.channel = connection.sessionid = room_id; + connection.session = { data: true, - oneway: true // --- to make it one-to-many + // oneway: true --- to make it one-to-many }; + connection.filesContainer = logsDiv; + connection.connectedWith = {}; + + connection.onmessage = function(event) { + if (event.data.doYouWannaReceiveThisFile) { + if (!connection.fileReceived[event.data.fileName]) { + connection.send({ + yesIWannaReceive: true, + fileName: event.data.fileName + }); + } + } + + if (event.data.yesIWannaReceive && !!lastSelectedFile) { + connection.shareFile(lastSelectedFile, event.userid); + } + }; + connection.onopen = function(e) { + try { + chrome.power.requestKeepAwake('display'); + } catch (e) {} + if (connection.connectedWith[e.userid]) return; connection.connectedWith[e.userid] = true; + var message = '' + e.userid + '
is connected.'; appendLog(message); + if (!lastSelectedFile) return; + // already shared the file + var file = lastSelectedFile; setTimeout(function() { - appendLog('Sharing file
' + file.name + 'Size: ' + bytesToSize(file.size) + '
With ' + connection.getAllParticipants().length + ' users'); - connection.shareFile(file); + appendLog('Sharing file
' + file.name + '
Size: ' + bytesToSize(file.size) + '
With ' + connection.getAllParticipants().length + ' users'); + + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: file.name + }); }, 500); }; + connection.onclose = function(e) { + incrementOrDecrementUsers(); + if (connection.connectedWith[e.userid]) return; + appendLog('Data connection has been closed between you and ' + e.userid + '. Re-Connecting..'); + connection.join(room_id); }; + connection.onerror = function(e) { if (connection.connectedWith[e.userid]) return; + appendLog('Data connection failed. between you and ' + e.userid + '. Retrying..'); }; + setFileProgressBarHandlers(connection); + connection.onUserStatusChanged = function(user) { - incrementOrDecrementUsers(user); + incrementOrDecrementUsers(); }; + connection.onleave = function(user) { user.status = 'offline'; connection.onUserStatusChanged(user); + incrementOrDecrementUsers(); }; + var message = 'Connecting room:
' + connection.channel + ''; appendLog(message); - connection.open(connection.channel, function() { - var message = 'Successfully connected to room:
' + connection.channel + ''; + + connection.openOrJoin(connection.channel, function(isRoomExists) { + var message = 'Successfully connected to room:
' + room_id + ''; + // if (isRoomEists) { } appendLog(message); + + document.querySelector('h1').innerHTML = 'Private Room URL: #' + room_id + ''; }); - document.querySelector('header').innerHTML = "Right-click to copy & share this private URL!"; + window.connection = connection; } function setFileProgressBarHandlers(connection) { var progressHelper = {}; + // www.RTCMultiConnection.org/docs/onFileStart/ connection.onFileStart = function(file) { if (connection.fileReceived[file.name]) return; - if(document.getElementById('file-' + file.name.replace('.', ''))) { - return; - } var div = document.createElement('div'); - div.id = 'file-' + file.name.replace('.', ''); - div.title = file.name; - div.innerHTML = ' '; - if (file.remoteUserId) { - div.innerHTML += ' (Sharing with:' + file.remoteUserId + ')'; + div.style.borderBottom = '1px solid black'; + div.style.padding = '2px 4px'; + div.id = file.uuid; + + var message = ''; + if (file.userid == connection.userid) { + message += 'Sharing with:' + file.remoteUserId; + } else { + message += 'Receiving from:' + file.userid; } + + message += '
' + file.name + '.'; + message += '
Size: ' + bytesToSize(file.size) + ''; + message += '
'; + + div.innerHTML = message; + connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + if (!file.remoteUserId) { progressHelper[file.uuid] = { div: div, @@ -117,9 +187,11 @@ function setFileProgressBarHandlers(connection) { progressHelper[file.uuid].progress.max = file.maxChunks; return; } + if (!progressHelper[file.uuid]) { progressHelper[file.uuid] = {}; } + progressHelper[file.uuid][file.remoteUserId] = { div: div, progress: div.querySelector('progress'), @@ -127,9 +199,11 @@ function setFileProgressBarHandlers(connection) { }; progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; }; + // www.RTCMultiConnection.org/docs/onFileProgress/ connection.onFileProgress = function(chunk) { if (connection.fileReceived[chunk.name]) return; + var helper = progressHelper[chunk.uuid]; if (!helper) { return; @@ -140,26 +214,33 @@ function setFileProgressBarHandlers(connection) { return; } } + helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; updateLabel(helper.progress, helper.label); }; + // www.RTCMultiConnection.org/docs/onFileEnd/ connection.onFileEnd = function(file) { if (connection.fileReceived[file.name]) return; - var div = document.getElementById('file-' + file.name.replace('.', '')); + + var div = document.getElementById(file.uuid); if (div) { div.parentNode.removeChild(div); } + if (file.remoteUserId === connection.userid) { previewFile(file); + connection.fileReceived[file.name] = file; + var message = 'Successfully received file'; message += '
' + file.name + '.'; message += '
Size: ' + bytesToSize(file.size) + '.'; message += '
Download'; - appendLog(message); + var div = appendLog(message); return; } + var message = 'Successfully shared file'; message += '
' + file.name + '.'; message += '
With: ' + file.remoteUserId + '.'; @@ -171,6 +252,7 @@ function setFileProgressBarHandlers(connection) { if (progress.position === -1) { return; } + var position = +progress.position.toFixed(2).split('.')[1] || 100; label.innerHTML = position + '%'; } @@ -189,33 +271,31 @@ function bytesToSize(bytes) { function onFileSelected(file) { var innerHTML = 'You selected:
' + file.name + '
Size: ' + bytesToSize(file.size) + ''; appendLog(innerHTML); + lastSelectedFile = file; + if (connection) { - connection.shareFile(file); + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: file.name + }); } } + var numberOfUsers = document.getElementById('number-of-users'); -function incrementOrDecrementUsers(user) { - if (!numberOfUsers.getAttribute('data-users')) { - numberOfUsers.setAttribute('data-users', ''); - } - if (numberOfUsers.getAttribute('data-users').indexOf(user.userid) !== -1 && user.status === 'offline') { - numberOfUsers.innerHTML = parseInt(numberOfUsers.innerHTML) - 1; - if (parseInt(numberOfUsers.innerHTML) < 0) { - numberOfUsers.innerHTML = 0; - } - return; - } - numberOfUsers.innerHTML = parseInt(numberOfUsers.innerHTML) + 1; - numberOfUsers.setAttribute('data-users', numberOfUsers.getAttribute('data-users') + ',' + user.userid); +function incrementOrDecrementUsers() { + numberOfUsers.innerHTML = connection ? connection.getAllParticipants().length : 0; } + var logsDiv = document.getElementById('logs'); function appendLog(html) { var div = document.createElement('div'); div.innerHTML = '

' + html + '

'; logsDiv.insertBefore(div, logsDiv.firstChild); + + return div; } function previewFile(file) { @@ -224,13 +304,16 @@ function previewFile(file) { btnSelectFile.style.zIndex = 10; btnSelectFile.style.top = '5px'; btnSelectFile.style.outline = 'none'; + document.querySelector('.overlay').style.display = 'none'; iframe.style.display = 'block'; + if (file.type.match(/image|video|audio|pdf|txt|javascript|css|php|py/g)) { iframe.src = URL.createObjectURL(file); } else { - iframe.src = 'https://i.imgur.com/2SUIhbf.png?1'; + iframe.src = 'https://cdn.webrtc-experiment.com/images/folder-icon.png'; } + iframe.onload = function() { Array.prototype.slice.call(iframe.contentWindow.document.body.querySelectorAll('*')).forEach(function(element) { element.style.maxWidth = '100%'; diff --git a/Chrome-Extensions/file-sharing/getExternalIceServers.js b/Chrome-Extensions/file-sharing/getExternalIceServers.js new file mode 100644 index 00000000..9fa5deec --- /dev/null +++ b/Chrome-Extensions/file-sharing/getExternalIceServers.js @@ -0,0 +1,2 @@ +// to get STUN/TURN URIs from xirsys.com +window.getExternalIceServers = true; \ No newline at end of file diff --git a/Chrome-Extensions/file-sharing/images/unknown.png b/Chrome-Extensions/file-sharing/images/unknown.png deleted file mode 100644 index 81280a24..00000000 Binary files a/Chrome-Extensions/file-sharing/images/unknown.png and /dev/null differ diff --git a/Chrome-Extensions/file-sharing/manifest.json b/Chrome-Extensions/file-sharing/manifest.json index 4782f1dd..e30dbf86 100755 --- a/Chrome-Extensions/file-sharing/manifest.json +++ b/Chrome-Extensions/file-sharing/manifest.json @@ -1,11 +1,11 @@ { "name" : "WebRTC File Sharing", "author": "Muaz Khan", - "version" : "1.9", + "version" : "2.1", "manifest_version" : 2, "minimum_chrome_version": "34", - "description" : "Instantly/Privately/Reliably share files with single or multiple users without uploading to server!", - "homepage_url": "https://www.webrtc-experiment.com/", + "description" : "Instantly/Privately/Reliably share files across all devices(mobile/desktop), among single or multiple users.", + "homepage_url": "https://rtcxp.com/fs", "background": { "scripts": ["background.js"], "persistent": false diff --git a/Chrome-Extensions/file-sharing/options.html b/Chrome-Extensions/file-sharing/options.html index 6853aa3e..f5bd8f40 100755 --- a/Chrome-Extensions/file-sharing/options.html +++ b/Chrome-Extensions/file-sharing/options.html @@ -45,7 +45,7 @@

Set Your Own Room ID:


E.g. You can always share this with screen viewers:
- https://www.webrtc-experiment.com/!!/?r=your_room_id + https://rtcxp.com/fs#your_room_id
diff --git a/Chrome-Extensions/file-sharing/rmc3.fbr.min.js b/Chrome-Extensions/file-sharing/rmc3.fbr.min.js new file mode 100644 index 00000000..921a3149 --- /dev/null +++ b/Chrome-Extensions/file-sharing/rmc3.fbr.min.js @@ -0,0 +1,3 @@ +// Last time updated: 2016-03-09 2:46:49 PM UTC + +function FileBufferReader(){function fbrClone(from,to){if(null==from||"object"!=typeof from)return from;if(from.constructor!=Object&&from.constructor!=Array)return from;if(from.constructor==Date||from.constructor==RegExp||from.constructor==Function||from.constructor==String||from.constructor==Number||from.constructor==Boolean)return new from.constructor(from);to=to||new from.constructor;for(var name in from)to[name]="undefined"==typeof to[name]?fbrClone(from[name],null):to[name];return to}var fbr=this,fbrHelper=new FileBufferReaderHelper;fbr.chunks={},fbr.users={},fbr.readAsArrayBuffer=function(file,earlyCallback,extra){if(!file.slice)return void console.warn("Not a real File object.",file);if(extra=extra||{userid:0},file.extra)if("string"==typeof file.extra)extra.extra=file.extra;else for(var e in file.extra)extra[e]=file.extra[e];extra.fileName=file.name,file.uuid&&(extra.fileUniqueId=file.uuid);var options={uuid:file.uuid||0,file:file,earlyCallback:earlyCallback,extra:extra,chunkSize:extra.chunkSize};fbrHelper.readAsArrayBuffer(fbr,options)},fbr.getNextChunk=function(fileUUID,callback,userid){var allFileChunks=fbr.chunks[fileUUID];if(allFileChunks){var currentPosition;"undefined"!=typeof userid?(fbr.users[userid+""]||(fbr.users[userid+""]={fileUUID:fileUUID,userid:userid,currentPosition:-1}),fbr.users[userid+""].currentPosition++,currentPosition=fbr.users[userid+""].currentPosition):(fbr.chunks[fileUUID].currentPosition++,currentPosition=fbr.chunks[fileUUID].currentPosition);var nextChunk=allFileChunks[currentPosition];nextChunk&&(nextChunk=fbrClone(nextChunk),"undefined"!=typeof userid&&(nextChunk.remoteUserId=userid+""),nextChunk.start&&fbr.onBegin(nextChunk),nextChunk.end&&fbr.onEnd(nextChunk),fbr.onProgress(nextChunk),fbr.convertToArrayBuffer(nextChunk,function(buffer){return nextChunk.currentPosition==nextChunk.maxChunks?void callback(buffer,!0):void callback(buffer,!1)}))}};var fbReceiver=new FileBufferReceiver(fbr);fbr.addChunk=function(chunk,callback){return chunk?void fbReceiver.receive(chunk,function(uuid){fbr.convertToArrayBuffer({readyForNextChunk:!0,uuid:uuid},callback)}):void console.error("Chunk is missing.")},fbr.onBegin=function(){},fbr.onEnd=function(){},fbr.onProgress=function(){},fbr.convertToObject=FileConverter.ConvertToObject,fbr.convertToArrayBuffer=FileConverter.ConvertToArrayBuffer,fbr.setMultipleUsers=function(){}}function FileBufferReaderHelper(){function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"}));return window.fileBufferWorker||(window.fileBufferWorker=new Worker(blob)),window.fileBufferWorker}function fileReaderWrapper(options,callback){function addChunks(fileName,binarySlice,addChunkCallback){numOfChunksInSlice=Math.ceil(binarySlice.byteLength/chunkSize);for(var i=0;numOfChunksInSlice>i;i++){var start=i*chunkSize;chunks[currentPosition]=binarySlice.slice(start,Math.min(start+chunkSize,binarySlice.byteLength)),callback({uuid:file.uuid,buffer:chunks[currentPosition],currentPosition:currentPosition,maxChunks:maxChunks,size:file.size,name:file.name||options.extra.fileName,lastModifiedDate:file.lastModifiedDate?file.lastModifiedDate.toString():"",type:file.type,extra:options.extra||options}),currentPosition++}currentPosition==maxChunks&&(hasEntireFile=!0),addChunkCallback()}callback=callback||function(chunk){postMessage(chunk)};var file=options.file;file.uuid||(file.uuid=options.fileUniqueId||(100*Math.random()).toString().replace(/\./g,""));var chunkSize=options.chunkSize||15e3,sliceId=0,cacheSize=chunkSize,chunksPerSlice=Math.floor(Math.min(1e8,cacheSize)/chunkSize),sliceSize=chunksPerSlice*chunkSize,maxChunks=Math.ceil(file.size/chunkSize);file.maxChunks=maxChunks;var numOfChunksInSlice,hasEntireFile,currentPosition=0,chunks=[];callback({currentPosition:currentPosition,uuid:file.uuid,maxChunks:maxChunks,size:file.size,name:file.name||options.extra.fileName,type:file.type,lastModifiedDate:file.lastModifiedDate?file.lastModifiedDate.toString():"",start:!0,extra:options.extra||options,url:URL.createObjectURL(file)});var blob,reader=new FileReader;reader.onloadend=function(evt){evt.target.readyState==FileReader.DONE&&addChunks(file.name,evt.target.result,function(){sliceId++,(sliceId+1)*sliceSize5&&5==chunk.currentPosition&&earlyCallback&&(earlyCallback(chunk.uuid),earlyCallback=null)}var earlyCallback=options.earlyCallback;if(delete options.earlyCallback,navigator.mozGetUserMedia&&(window.___Worker=window.Worker,delete window.Worker),window.Worker&&"function"==typeof Worker){var webWorker=processInWebWorker(fileReaderWrapper);webWorker.onmessage=function(event){processChunk(event.data)},webWorker.postMessage(options)}else fileReaderWrapper(options,processChunk),navigator.mozGetUserMedia&&(window.Worker=window.___Worker)}}function FileBufferReceiver(fbr){function receive(chunk,callback){if(!chunk.uuid)return void fbr.convertToObject(chunk,function(object){receive(object)});if(chunk.start&&!packets[chunk.uuid]&&(packets[chunk.uuid]=[],missedChunks[chunk.uuid]&&(packets[chunk.uuid].push(chunk.buffer),missedChunks[chunk.uuid].forEach(function(chunk){receive(chunk,callback)}),delete missedChunks[chunk.uuid]),fbr.onBegin&&fbr.onBegin(chunk)),!chunk.end&&chunk.buffer){if(!packets[chunk.uuid])return missedChunks[chunk.uuid]||(missedChunks[chunk.uuid]=[]),void missedChunks[chunk.uuid].push(chunk);-1==packets[chunk.uuid].indexOf(chunk.buffer)&&packets[chunk.uuid].push(chunk.buffer)}if(chunk.end){for(var _packets=packets[chunk.uuid],finalArray=[],length=_packets.length,i=0;length>i;i++)_packets[i]&&finalArray.push(_packets[i]);var blob=new Blob(finalArray,{type:chunk.type});blob=merge(blob,chunk),blob.url=URL.createObjectURL(blob),blob.uuid=chunk.uuid||blob.extra.fileUniqueId,blob.name=blob.name||blob.extra.fileName,blob.size||console.error("Something went wrong. Blob Size is 0."),fbr.onEnd&&fbr.onEnd(blob)}chunk.buffer&&fbr.onProgress&&fbr.onProgress(chunk),chunk.end||callback(chunk.uuid)}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}var packets={},missedChunks=[];this.receive=receive}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}window.FileSelector=function(){function selectFile(callback,multiple){var file=document.createElement("input");file.type="file",multiple&&(file.multiple=!0),file.onchange=function(){return multiple?file.files.length?void callback(file.files):void console.error("No file selected."):file.files[0]?(callback(file.files[0]),void file.parentNode.removeChild(file)):void console.error("No file selected.")},file.style.display="none",(document.body||document.documentElement).appendChild(file),fireClickEvent(file)}function fireClickEvent(element){var evt=new window.MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,button:0,buttons:0,mozInputSource:1});element.dispatchEvent(evt)}var selector=this;selector.selectSingleFile=selectFile,selector.selectMultipleFiles=function(callback){selectFile(callback,!0)}};var FileConverter={ConvertToArrayBuffer:function(object,callback){binarize.pack(object,function(dataView){callback(dataView.buffer)})},ConvertToObject:function(buffer,callback){binarize.unpack(buffer,callback)}};!function(root){var debug=!1,BIG_ENDIAN=!1,LITTLE_ENDIAN=!0,TYPE_LENGTH=Uint8Array.BYTES_PER_ELEMENT,LENGTH_LENGTH=Uint16Array.BYTES_PER_ELEMENT,BYTES_LENGTH=Uint32Array.BYTES_PER_ELEMENT,Types={NULL:0,UNDEFINED:1,STRING:2,NUMBER:3,BOOLEAN:4,ARRAY:5,OBJECT:6,INT8ARRAY:7,INT16ARRAY:8,INT32ARRAY:9,UINT8ARRAY:10,UINT16ARRAY:11,UINT32ARRAY:12,FLOAT32ARRAY:13,FLOAT64ARRAY:14,ARRAYBUFFER:15,BLOB:16,FILE:16,BUFFER:17};if(debug)var TypeNames=["NULL","UNDEFINED","STRING","NUMBER","BOOLEAN","ARRAY","OBJECT","INT8ARRAY","INT16ARRAY","INT32ARRAY","UINT8ARRAY","UINT16ARRAY","UINT32ARRAY","FLOAT32ARRAY","FLOAT64ARRAY","ARRAYBUFFER","BLOB","BUFFER"];var Length=[null,null,"Uint16","Float64","Uint8",null,null,"Int8","Int16","Int32","Uint8","Uint16","Uint32","Float32","Float64","Uint8","Uint8","Uint8"],binary_dump=function(view,start,length){var table=[],endianness=BIG_ENDIAN,ROW_LENGTH=40;table[0]=[];for(var i=0;ROW_LENGTH>i;i++)table[0][i]=10>i?"0"+i.toString(10):i.toString(10);for(i=0;length>i;i++){var code=view.getUint8(start+i,endianness),index=~~(i/ROW_LENGTH)+1;"undefined"==typeof table[index]&&(table[index]=[]),table[index][i%ROW_LENGTH]=16>code?"0"+code.toString(16):code.toString(16)}for(console.log("%c"+table[0].join(" "),"font-weight: bold;"),i=1;ij;j++,cursor+=unit)view.setUint16(cursor,value.charCodeAt(j),endianness);break;case Types.NUMBER:case Types.BOOLEAN:debug&&console.info("%c"+value.toString(),"font-weight:bold;"),view["set"+type_name](cursor,value,endianness),cursor+=unit;break;case Types.INT8ARRAY:case Types.INT16ARRAY:case Types.INT32ARRAY:case Types.UINT8ARRAY:case Types.UINT16ARRAY:case Types.UINT32ARRAY:case Types.FLOAT32ARRAY:case Types.FLOAT64ARRAY:var _view=new Uint8Array(view.buffer,cursor,byte_length);_view.set(new Uint8Array(value.buffer)),cursor+=byte_length;break;case Types.ARRAYBUFFER:case Types.BUFFER:var _view=new Uint8Array(view.buffer,cursor,byte_length);_view.set(new Uint8Array(value)),cursor+=byte_length;break;case Types.BLOB:case Types.ARRAY:case Types.OBJECT:break;default:throw"TypeError: Unexpected type found."}debug&&binary_dump(view,start,cursor-start)}return view},unpack=function(view,cursor){var type,length,byte_length,value,elem,i=0,endianness=BIG_ENDIAN,start=cursor;type=view.getUint8(cursor,endianness),cursor+=TYPE_LENGTH,debug&&console.info("Unpacking",type,TypeNames[type]),(type===Types.ARRAY||type===Types.OBJECT)&&(length=view.getUint16(cursor,endianness),cursor+=LENGTH_LENGTH,debug&&console.info("Content Length",length)),byte_length=view.getUint32(cursor,endianness),cursor+=BYTES_LENGTH,debug&&console.info("Byte Length",byte_length,"bytes");var type_name=Length[type],unit=null===type_name?0:root[type_name+"Array"].BYTES_PER_ELEMENT;switch(type){case Types.NULL:case Types.UNDEFINED:debug&&binary_dump(view,start,cursor-start),value=null;break;case Types.STRING:length=byte_length/unit;var string=[];for(i=0;length>i;i++){var code=view.getUint16(cursor,endianness);cursor+=unit,string.push(String.fromCharCode(code))}value=string.join(""),debug&&(console.info('Actual Content %c"'+value+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.NUMBER:value=view.getFloat64(cursor,endianness),cursor+=unit,debug&&(console.info('Actual Content %c"'+value.toString()+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.BOOLEAN:value=1===view.getUint8(cursor,endianness)?!0:!1,cursor+=unit,debug&&(console.info('Actual Content %c"'+value.toString()+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.INT8ARRAY:case Types.INT16ARRAY:case Types.INT32ARRAY:case Types.UINT8ARRAY:case Types.UINT16ARRAY:case Types.UINT32ARRAY:case Types.FLOAT32ARRAY:case Types.FLOAT64ARRAY:case Types.ARRAYBUFFER:elem=view.buffer.slice(cursor,cursor+byte_length),cursor+=byte_length,value=type===Types.ARRAYBUFFER?elem:new root[type_name+"Array"](elem),debug&&binary_dump(view,start,cursor-start);break;case Types.BLOB:if(debug&&binary_dump(view,start,cursor-start),root.Blob){var mime=unpack(view,cursor),buffer=unpack(view,mime.cursor);cursor=buffer.cursor,value=new Blob([buffer.value],{type:mime.value})}else elem=view.buffer.slice(cursor,cursor+byte_length),cursor+=byte_length,value=new Buffer(elem);break;case Types.ARRAY:for(debug&&binary_dump(view,start,cursor-start),value=[],i=0;length>i;i++)elem=unpack(view,cursor),cursor=elem.cursor,value.push(elem.value);break;case Types.OBJECT:for(debug&&binary_dump(view,start,cursor-start),value={},i=0;length>i;i++){var key=unpack(view,cursor),val=unpack(view,key.cursor);cursor=val.cursor,value[key.value]=val.value}break;default:throw"TypeError: Type not supported."}return{value:value,cursor:cursor}},deferredSerialize=function(array,callback){for(var length=array.length,results=[],count=0,byte_length=0,i=0;ii;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function getRMCMediaElement(stream,callback,connection){var isAudioOnly=!1;stream.getVideoTracks().length||(isAudioOnly=!0);var mediaElement=document.createElement(isAudioOnly?"audio":"video");return isPluginRTC?(connection.videosContainer.insertBefore(mediaElement,connection.videosContainer.firstChild),void setTimeout(function(){Plugin.attachMediaStream(mediaElement,stream),callback(mediaElement)},1e3)):(mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:window.URL.createObjectURL(stream),mediaElement.controls=!0,isFirefox&&mediaElement.addEventListener("ended",function(){if(currentUserMediaRequest.remove(stream.idInstance),"local"===stream.type){StreamsHandler.onSyncNeeded(stream.streamid,"ended"),connection.attachStreams.forEach(function(aStream,idx){stream.streamid===aStream.streamid&&delete connection.attachStreams[idx]});var newStreamsArray=[];connection.attachStreams.forEach(function(aStream){aStream&&newStreamsArray.push(aStream)}),connection.attachStreams=newStreamsArray,connection.observers.all();var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent)return void connection.onstreamended(streamEvent);this.parentNode&&this.parentNode.removeChild(this)}},!1),mediaElement.play(),void callback(mediaElement))}function listenEventHandler(eventName,eventHandler){window.removeEventListener(eventName,eventHandler),window.addEventListener(eventName,eventHandler,!1)}function removeNullEntries(array){var newArray=[];return array.forEach(function(item){item&&newArray.push(item)}),newArray}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function observeObject(obj,callback){Object.observe&&(isMobileDevice||Object.observe(obj,function(changes){var jsonStringified=JSON.stringify(changes);lastChanges!=jsonStringified&&(lastChanges=jsonStringified,callback(changes))}))}function setCordovaAPIs(){if("iOS"===DetectRTC.osName&&"undefined"!=typeof cordova&&"undefined"!=typeof cordova.plugins&&"undefined"!=typeof cordova.plugins.iosrtc){var iosrtc=cordova.plugins.iosrtc;window.webkitRTCPeerConnection=iosrtc.RTCPeerConnection,window.RTCSessionDescription=iosrtc.RTCSessionDescription,window.RTCIceCandidate=iosrtc.RTCIceCandidate,window.MediaStream=iosrtc.MediaStream,window.MediaStreamTrack=iosrtc.MediaStreamTrack,navigator.getUserMedia=navigator.webkitGetUserMedia=iosrtc.getUserMedia,iosrtc.debug.enable("iosrtc*"),iosrtc.registerGlobals()}}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function onPluginRTCInitialized(pluginRTCObject){Plugin=pluginRTCObject,MediaStreamTrack=Plugin.MediaStreamTrack,RTCPeerConnection=Plugin.RTCPeerConnection,RTCIceCandidate=Plugin.RTCIceCandidate,RTCSessionDescription=Plugin.RTCSessionDescription}function PeerInitiator(config){function createDataChannel(){if(!isOfferer)return void(peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)});var channel=peer.createDataChannel("RTCDataChannel",{});setChannelEvents(channel)}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra,this.userid=config.userid,this.streams=[],this.channels=[],this.connectionDescription=config.connectionDescription;var that=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};if(Object.observe){var that=this;Object.observe(this.channels,function(changes){changes.forEach(function(change){"add"===change.type&&change.object[change.name].addEventListener("close",function(){delete that.channels[that.channels.indexOf(change.object[change.name])],that.channels=removeNullEntries(that.channels)},!1),("remove"===change.type||"delete"===change.type)&&-1!==that.channels.indexOf(change.object[change.name])&&delete that.channels.indexOf(change.object[change.name]),that.channels=removeNullEntries(that.channels)})})}defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer?(peer=config.peerRef,peer.getLocalStreams().forEach(function(stream){localStreams.forEach(function(localStream,index){stream==localStream&&delete localStreams[index]}),connection.removeStreams.forEach(function(streamToRemove,index){stream===streamToRemove&&(stream=connection.beforeRemovingStream(stream),stream&&peer.removeStream&&peer.removeStream(stream),localStreams.forEach(function(localStream,index){streamToRemove==localStream&&delete localStreams[index]}))})})):peer=new RTCPeerConnection(navigator.onLine?{iceServers:connection.iceServers,iceTransports:"all"}:null,connection.optionalArgument),"Firefox"===connection.DetectRTC.browser.name&&(peer.removeStream=function(stream){stream.mute(),connection.StreamsHandler.onSyncNeeded(stream.streamid,"stream-removed")}),peer.onicecandidate=function(event){event.candidate&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex})};var isFirefoxOffered=!isFirefox;config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered&&(isFirefoxOffered=!0),localStreams.forEach(function(localStream){config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream||config.dontAttachLocalStream||(localStream=connection.beforeAddingStream(localStream),localStream&&peer.addStream(localStream))}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=that.extra;connection.peers[that.userid]&&(extra=connection.peers[that.userid].extra||extra),peer&&config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:that.userid})};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),peer.onaddstream=function(event){var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare&&(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen),event.stream.streamid=event.stream.id,event.stream.stop||(event.stream.stop=function(){isFirefox&&fireEvent(this,"ended")}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)},peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp){remoteSdp.sdp=connection.processSdp(remoteSdp.sdp),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),function(){},function(error){connection.enableLogs&&console.error(JSON.stringify(error,null," "))})};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),connection.session.data===!0&&createDataChannel(),config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp)),("two-way"==connection.session.audio||"two-way"==connection.session.video||"two-way"==connection.session.screen)&&(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),peer[isOfferer?"createOffer":"createAnswer"](function(localSdp){localSdp.sdp=connection.processSdp(localSdp.sdp),peer.setLocalDescription(localSdp),config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})},function(error){connection.enableLogs&&console.error("sdp-error",error)},defaults.sdpConstraints),peer.nativeClose=peer.close,peer.close=function(){if(peer){try{-1===peer.iceConnectionState.search(/closed|failed/gi)&&peer.getRemoteStreams().forEach(function(stream){ +stream.stop()}),peer.nativeClose()}catch(e){}peer=null,that.peer=null}},this.peer=peer}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream),options.onGettingLocalMedia(stream,returnBack),stream.addEventListener("ended",function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift())}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{if(isPluginRTC){document.createElement("video");return void Plugin.getUserMedia({audio:!0,video:!0},function(stream){stream.streamid=stream.id||getRandomString(),streaming(stream)},function(error){})}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);"undefined"!=typeof cordova&&(isMobileDevice=!0,isChrome=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0,isChrome=!0);var isPluginRTC=!isMobileDevice&&(isSafari||isIE),chromeVersion=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),50),matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10)),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){!this.getAudioTracks&&this.getTracks&&(this.getAudioTracks=function(){var array=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&array.push(track)}),array}),!this.getVideoTracks&&this.getTracks&&(this.getVideoTracks=function(){var array=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&array.push(track)}),array}),this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()}),isFirefox&&fireEvent(this,"ended")}),"undefined"!=typeof MediaStream&&("getAudioTracks"in MediaStream.prototype&&"function"==typeof MediaStream.prototype.getAudioTracks||(MediaStream.prototype.getAudioTracks=function(){}),"getVideoTracks"in MediaStream.prototype&&"function"==typeof MediaStream.prototype.getVideoTracks||(MediaStream.prototype.getVideoTracks=function(){}));var lastChanges="";!function(){function LoadPluginRTC(){function getPlugin(){return document.getElementById("WebrtcEverywherePluginId")}window.PluginRTC={};var extractPluginObj=function(elt){return elt.isWebRtcPlugin?elt:elt.pluginObj},attachEventListener=function(elt,type,listener,useCapture){var _pluginObj=extractPluginObj(elt);_pluginObj?_pluginObj.bindEventListener(type,listener,useCapture):"undefined"!=typeof elt.addEventListener?elt.addEventListener(type,listener,useCapture):"undefined"!=typeof elt.addEvent&&elt.addEventListener("on"+type,listener,useCapture)},installPlugin=function(){if(!document.getElementById("WebrtcEverywherePluginId")){var pluginObj=document.createElement("object");isIE?pluginObj.setAttribute("classid","CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53"):pluginObj.setAttribute("type","application/webrtc-everywhere"),pluginObj.setAttribute("id","WebrtcEverywherePluginId"),(document.body||document.documentElement).appendChild(pluginObj),pluginObj.setAttribute("width","0"),pluginObj.setAttribute("height","0")}};document.body?installPlugin():(attachEventListener(window,"load",function(){installPlugin()}),attachEventListener(document,"readystatechange",function(){"complete"==document.readyState&&installPlugin()}));var getUserMediaDelayed;window.PluginRTC.getUserMedia=navigator.getUserMedia=function(constraints,successCallback,errorCallback){"complete"!==document.readyState?getUserMediaDelayed||(getUserMediaDelayed=!0,attachEventListener(document,"readystatechange",function(){getUserMediaDelayed&&"complete"==document.readyState&&(getUserMediaDelayed=!1,getPlugin().getUserMedia(constraints,successCallback,errorCallback))})):getPlugin().getUserMedia(constraints,successCallback,errorCallback)},window.PluginRTC.attachMediaStream=function(element,stream){if(element.isWebRtcPlugin)return element.src=stream,element;if("video"===element.nodeName.toLowerCase()){if(!element.pluginObj&&stream){var _pluginObj=document.createElement("object"),_isIE=Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(window,"ActiveXObject")||"ActiveXObject"in window;_isIE?_pluginObj.setAttribute("classid","CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53"):_pluginObj.setAttribute("type","application/webrtc-everywhere"),element.pluginObj=_pluginObj,_pluginObj.setAttribute("className",element.className),_pluginObj.setAttribute("innerHTML",element.innerHTML);var width=element.getAttribute("width"),height=element.getAttribute("height"),bounds=element.getBoundingClientRect();if(width||(width=bounds.right-bounds.left),height||(height=bounds.bottom-bounds.top),"getComputedStyle"in window){var computedStyle=window.getComputedStyle(element,null);width||"auto"==computedStyle.width||"0px"==computedStyle.width||(width=computedStyle.width),height||"auto"==computedStyle.height||"0px"==computedStyle.height||(height=computedStyle.height)}width?_pluginObj.setAttribute("width",width):_pluginObj.setAttribute("autowidth",!0),height?_pluginObj.setAttribute("height",height):_pluginObj.setAttribute("autoheight",!0),(document.body||document.documentElement).appendChild(_pluginObj),element.parentNode&&(element.parentNode.replaceChild(_pluginObj,element),document.body.appendChild(element),element.style.visibility="hidden")}return element.pluginObj&&(element.pluginObj.bindEventListener("play",function(objvid){element.pluginObj&&(element.pluginObj.getAttribute("autowidth")&&objvid.videoWidth&&element.pluginObj.setAttribute("width",objvid.videoWidth),element.pluginObj.getAttribute("autoheight")&&objvid.videoHeight&&element.pluginObj.setAttribute("height",objvid.videoHeight))}),element.pluginObj.src=stream),element.pluginObj}return"audio"===element.nodeName.toLowerCase()?element:void 0},window.PluginRTC.MediaStreamTrack={};var getSourcesDelayed;window.PluginRTC.MediaStreamTrack.getSources=function(gotSources){"complete"!==document.readyState?getSourcesDelayed||(getSourcesDelayed=!0,attachEventListener(document,"readystatechange",function(){getSourcesDelayed&&"complete"==document.readyState&&(getSourcesDelayed=!1,getPlugin().getSources(gotSources))})):getPlugin().getSources(gotSources)},window.PluginRTC.RTCPeerConnection=function(configuration,constraints){return getPlugin().createPeerConnection(configuration,constraints)},window.PluginRTC.RTCIceCandidate=function(RTCIceCandidateInit){return getPlugin().createIceCandidate(RTCIceCandidateInit)},window.PluginRTC.RTCSessionDescription=function(RTCSessionDescriptionInit){return getPlugin().createSessionDescription(RTCSessionDescriptionInit)},window.onPluginRTCInitialized&&window.onPluginRTCInitialized(window.PluginRTC)}var ua=navigator.userAgent.toLowerCase(),isSafari=-1!=ua.indexOf("safari")&&-1==ua.indexOf("chrome"),isIE=!!(Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(window,"ActiveXObject")||"ActiveXObject"in window);(isSafari||isIE)&&window.addEventListener("load",LoadPluginRTC,!1)}(),function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("MSIE"),browserName="IE",fullVersion=nAgt.substring(verOffset+5)):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10).toString()),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return match&&parseInt(match[1],10)>=10?!0:!1}function detectPrivateMode(callback){var isPrivate;if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){console.log(e),isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test")}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState?!0:!1},function(isTimeout){isTimeout||(isPrivate=db.result?!1:!0)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}retry(function(){return"undefined"!=typeof isPrivate?!0:!1},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){var unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(var id in clientStrings){var cs=clientStrings[id];if(cs.r.test(nAgt)){os=cs.s;break}}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function DetectLocalIPAddress(callback){DetectRTC.isWebRTCSupported&&(DetectRTC.isORTCSupported||getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)}))}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(!match)return void console.warn("Could not match IP address in",candidate);var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}if(RTCPeerConnection){var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}}function checkDeviceSupport(callback){if(canEnumerate){if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];"audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput");var skip;MediaDevices.forEach(function(d){d.id===device.id&&d.kind===device.kind&&(skip=!0)}),skip||(device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.label="Please invoke getUserMedia once.","https:"!==location.protocol&&document.domain.search&&-1===document.domain.search(/localhost|127.0./g)&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,-1===audioInputDevices.indexOf(device)&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,-1===audioOutputDevices.indexOf(device)&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,-1===videoInputDevices.indexOf(device)&&videoInputDevices.push(device)),-1===MediaDevices.indexOf(device)&&MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}}var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:"Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45"};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode&&!isEdge,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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version";if(isMobile.any())osName=isMobile.getOsName();else{var osInfo=detectDesktopOS();osName=osInfo.osName,osVersion=osInfo.osVersion}var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;checkDeviceSupport();var DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),"https:"!==location.protocol&&(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&"https:"!==location.protocol&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}(),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:(console.error("WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser."),RTCPeerConnection=window.RTCSessionDescription=window.RTCIceCandidate=function(){});var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,Plugin={};"undefined"!=typeof PluginRTC&&onPluginRTCInitialized(PluginRTC);var CodecsHandler=function(){function removeVPX(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtpmap:100 VP8/90000\r\n",""),sdp=sdp.replace("a=rtpmap:101 VP9/90000\r\n",""),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace("a=rtcp-fb:120 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 ccm fir\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 ccm fir\r\n","")}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;ii;++i)if(0===sdpLines[i].indexOf(prefix)&&(!substr||-1!==sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())))return i;return null}function getCodecPayloadType(sdpLine){var pattern=new RegExp("a=rtpmap:(\\d+) \\w+\\/\\d+"),result=sdpLine.match(pattern);return result&&2===result.length?result[1]:null}function setVideoBitrates(sdp,params){if(isMobileDevice)return sdp;params=params||{};var vp8Payload,xgoogle_min_bitrate=params.min,xgoogle_max_bitrate=params.max,sdpLines=sdp.split("\r\n"),vp8Index=findLine(sdpLines,"a=rtpmap","VP8/90000");if(vp8Index&&(vp8Payload=getCodecPayloadType(sdpLines[vp8Index])),!vp8Payload)return sdp;var rtxPayload,rtxIndex=findLine(sdpLines,"a=rtpmap","rtx/90000");if(rtxIndex&&(rtxPayload=getCodecPayloadType(sdpLines[rtxIndex])),!rtxIndex)return sdp;var rtxFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+rtxPayload.toString());if(null!==rtxFmtpLineIndex){var appendrtxNext="\r\n";appendrtxNext+="a=fmtp:"+vp8Payload+" x-google-min-bitrate="+(xgoogle_min_bitrate||"228")+"; x-google-max-bitrate="+(xgoogle_max_bitrate||"228"), +sdpLines[rtxFmtpLineIndex]=sdpLines[rtxFmtpLineIndex].concat(appendrtxNext),sdp=sdpLines.join("\r\n")}return sdp}function setOpusAttributes(sdp,params){if(isMobileDevice)return sdp;params=params||{};var opusPayload,sdpLines=sdp.split("\r\n"),opusIndex=findLine(sdpLines,"a=rtpmap","opus/48000");if(opusIndex&&(opusPayload=getCodecPayloadType(sdpLines[opusIndex])),!opusPayload)return sdp;var opusFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+opusPayload.toString());if(null===opusFmtpLineIndex)return sdp;var appendOpusNext="";return appendOpusNext+="; stereo="+("undefined"!=typeof params.stereo?params.stereo:"1"),appendOpusNext+="; sprop-stereo="+("undefined"!=typeof params["sprop-stereo"]?params["sprop-stereo"]:"1"),"undefined"!=typeof params.maxaveragebitrate&&(appendOpusNext+="; maxaveragebitrate="+(params.maxaveragebitrate||1048576)),"undefined"!=typeof params.maxplaybackrate&&(appendOpusNext+="; maxplaybackrate="+(params.maxplaybackrate||1048576)),"undefined"!=typeof params.cbr&&(appendOpusNext+="; cbr="+("undefined"!=typeof params.cbr?params.cbr:"1")),"undefined"!=typeof params.useinbandfec&&(appendOpusNext+="; useinbandfec="+params.useinbandfec),"undefined"!=typeof params.usedtx&&(appendOpusNext+="; usedtx="+params.usedtx),"undefined"!=typeof params.maxptime&&(appendOpusNext+="\r\na=maxptime:"+params.maxptime),sdpLines[opusFmtpLineIndex]=sdpLines[opusFmtpLineIndex].concat(appendOpusNext),sdp=sdpLines.join("\r\n")}function preferVP9(sdp){return-1===sdp.indexOf("SAVPF 100 101")||-1===sdp.indexOf("VP9/90000")?sdp:sdp.replace("SAVPF 100 101","SAVPF 101 100")}var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);return"undefined"!=typeof cordova&&(isMobileDevice=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0),{removeVPX:removeVPX,disableNACK:disableNACK,prioritize:prioritize,removeNonG722:removeNonG722,setApplicationSpecificBandwidth:function(sdp,bandwidth,isScreen){return setBAS(sdp,bandwidth,isScreen)},setVideoBitrates:function(sdp,params){return setVideoBitrates(sdp,params)},setOpusAttributes:function(sdp,params){return setOpusAttributes(sdp,params)},preferVP9:preferVP9}}();window.BandwidthHandler=CodecsHandler;var loadedIceFrame,OnIceCandidateHandler=function(){function processCandidates(connection,icePair){var candidate=icePair.candidate,iceRestrictions=connection.candidates,stun=iceRestrictions.stun,turn=iceRestrictions.turn;if(isNull(iceRestrictions.reflexive)||(stun=iceRestrictions.reflexive),isNull(iceRestrictions.relay)||(turn=iceRestrictions.relay),(iceRestrictions.host||!candidate.match(/typ host/g))&&(turn||!candidate.match(/typ relay/g))&&(stun||!candidate.match(/typ srflx/g))){var protocol=connection.iceProtocols;if((protocol.udp||!candidate.match(/ udp /g))&&(protocol.tcp||!candidate.match(/ tcp /g)))return connection.enableLogs&&console.debug("Your candidate pairs:",candidate),{candidate:candidate,sdpMid:icePair.sdpMid,sdpMLineIndex:icePair.sdpMLineIndex}}}return{processCandidates:processCandidates}}();"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&loadIceFrame(function(externalIceServers){externalIceServers&&externalIceServers.length&&(window.RMCExternalIceServers=externalIceServers,window.iceServersLoadCallback&&"function"==typeof window.iceServersLoadCallback&&window.iceServersLoadCallback(externalIceServers))});var IceServersHandler=function(){function getIceServers(connection){var iceServers=[];return iceServers.push({urls:"stun:stun.l.google.com:19302"},{urls:"stun:mmt-stun.verkstad.net"},{urls:"stun:stun.anyfirewall.com:3478"}),iceServers.push({urls:"turn:turn.bistri.com:80",credential:"homeo",username:"homeo"}),iceServers.push({urls:"turn:turn.anyfirewall.com:443",credential:"webrtc",username:"webrtc"}),iceServers.push({urls:"turn:mmt-turn.verkstad.net",username:"webrtc",credential:"secret"}),window.RMCExternalIceServers?(iceServers=window.RMCExternalIceServers.concat(iceServers),connection.iceServers=iceServers):"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&(window.iceServersLoadCallback=function(){iceServers=window.RMCExternalIceServers.concat(iceServers),connection.iceServers=iceServers}),iceServers}return{getIceServers:getIceServers}}(),getUserMedia=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils=window.webrtcUtils||{};if(webrtcUtils.enableLogs||(webrtcUtils.enableLogs=!0),webrtcUtils.log||(webrtcUtils.log=function(){webrtcUtils.enableLogs&&("undefined"!=typeof module||"function"==typeof require&&"function"==typeof define||console.log.apply(console,arguments))}),webrtcUtils.extractVersion||(webrtcUtils.extractVersion=function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10)}),"object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=stream?URL.createObjectURL(stream):null)}}),getUserMedia=window.navigator&&window.navigator.getUserMedia),"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia&&window.mozRTCPeerConnection){if(webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,getUserMedia=function(constraints,onSuccess,onError){var constraintsToFF37=function(c){if("object"!=typeof c||c.require)return c;var require=[];return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r=c[key]="object"==typeof c[key]?c[key]:{ideal:c[key]};if((void 0!==r.min||void 0!==r.max||void 0!==r.exact)&&require.push(key),void 0!==r.exact&&("number"==typeof r.exact?r.min=r.max=r.exact:c[key]=r.exact,delete r.exact),void 0!==r.ideal){c.advanced=c.advanced||[];var oc={};"number"==typeof r.ideal?oc[key]={min:r.ideal,max:r.ideal}:oc[key]=r.ideal,c.advanced.push(oc),delete r.ideal,Object.keys(r).length||delete c[key]}}}),require.length&&(c.require=require),c};return 38>webrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,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)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38;var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")})}else navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)?(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=12):webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcDetectedBrowser="not a browser";"undefined"!=typeof module?module.exports={getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}:"function"==typeof require&&"function"==typeof define&&define([],function(){return{getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}}),"undefined"!=typeof webrtcUtils&&(webrtcUtils.enableLogs=!1);var currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[],remove:function(idInstance){this.mutex=!1;var stream=this.streams[idInstance];if(stream){stream=stream.stream;var options=stream.currentUserMediaRequestOptions;this.queueRequests.indexOf(options)&&(delete this.queueRequests[this.queueRequests.indexOf(options)],this.queueRequests=removeNullEntries(this.queueRequests)),this.streams[idInstance].stream=null,delete this.streams[idInstance]}}},StreamsHandler=function(){function handleType(type){return type?"string"==typeof type||"undefined"==typeof type?type:type.audio&&type.video?null:type.audio?"audio":type.video?"video":void 0:void 0}function setHandlers(stream,syncAction,connection){function graduallyIncreaseVolume(){if(connection.streamEvents[stream.streamid].mediaElement){var mediaElement=connection.streamEvents[stream.streamid].mediaElement;mediaElement.volume=0,afterEach(200,5,function(){mediaElement.volume+=.2})}}("undefined"==typeof syncAction||1==syncAction)&&stream.addEventListener("ended",function(){StreamsHandler.onSyncNeeded(this.streamid,"ended")},!1),stream.mute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!1,connection.streamEvents[stream.streamid].isAudioMuted=!0}),("undefined"==typeof type||"video"==type)&&stream.getVideoTracks().forEach(function(track){track.enabled=!1}),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"mute",type),connection.streamEvents[stream.streamid].muteType=type||"both",fireEvent(stream,"mute",type)},stream.unmute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),graduallyIncreaseVolume(),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!0,connection.streamEvents[stream.streamid].isAudioMuted=!1}),("undefined"==typeof type||"video"==type)&&(stream.getVideoTracks().forEach(function(track){track.enabled=!0}),"undefined"!=typeof type&&"video"==type&&connection.streamEvents[stream.streamid].isAudioMuted&&!function looper(times){times||(times=0),times++,100>times&&connection.streamEvents[stream.streamid].isAudioMuted&&(stream.mute("audio"),setTimeout(function(){looper(times)},50))}()),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"unmute",type),connection.streamEvents[stream.streamid].unmuteType=type||"both",fireEvent(stream,"unmute",type)}}function afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes){startedTimes=(startedTimes||0)+1,startedTimes>=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();!function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}(),function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}if(-1!==document.domain.indexOf("webrtc-experiment.com")){window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}}();var TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(-1!==progress.position){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(!chunk.remoteUserId||(helper=progressHelper[chunk.uuid][chunk.remoteUserId]))&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exists.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;-1!=file.type.indexOf("image")?div.innerHTML='Download '+file.name+'
':div.innerHTML='Download '+file.name+'
'}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"===response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}}}return{handle:handle}}();window.RTCMultiConnection=RTCMultiConnection}(); \ No newline at end of file diff --git a/Chrome-Extensions/file-sharing/socket.io.js b/Chrome-Extensions/file-sharing/socket.io.js index 9e0254f1..955e1905 100644 --- a/Chrome-Extensions/file-sharing/socket.io.js +++ b/Chrome-Extensions/file-sharing/socket.io.js @@ -1,3154 +1,2 @@ -/*! Socket.IO.js build:0.9.16, development. Copyright(c) 2011 LearnBoost MIT Licensed */ - -var io = ('undefined' === typeof module ? {} : module.exports); -(function() { - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, global) { - - /** - * IO namespace. - * - * @namespace - */ - - var io = exports; - - /** - * Socket.IO version - * - * @api public - */ - - io.version = '0.9.16'; - - /** - * Protocol implemented. - * - * @api public - */ - - io.protocol = 1; - - /** - * Available transports, these will be populated with the available transports - * - * @api public - */ - - io.transports = []; - - /** - * Keep track of jsonp callbacks. - * - * @api private - */ - - io.j = []; - - /** - * Keep track of our io.Sockets - * - * @api private - */ - io.sockets = {}; - - - /** - * Manages connections to hosts. - * - * @param {String} uri - * @Param {Boolean} force creation of new socket (defaults to false) - * @api public - */ - - io.connect = function (host, details) { - var uri = io.util.parseUri(host) - , uuri - , socket; - - if (global && global.location) { - uri.protocol = uri.protocol || global.location.protocol.slice(0, -1); - uri.host = uri.host || (global.document - ? global.document.domain : global.location.hostname); - uri.port = uri.port || global.location.port; - } - - uuri = io.util.uniqueUri(uri); - - var options = { - host: uri.host - , secure: 'https' == uri.protocol - , port: uri.port || ('https' == uri.protocol ? 443 : 80) - , query: uri.query || '' - }; - - io.util.merge(options, details); - - if (options['force new connection'] || !io.sockets[uuri]) { - socket = new io.Socket(options); - } - - if (!options['force new connection'] && socket) { - io.sockets[uuri] = socket; - } - - socket = socket || io.sockets[uuri]; - - // if path is different from '' or / - return socket.of(uri.path.length > 1 ? uri.path : ''); - }; - -})('object' === typeof module ? module.exports : (this.io = {}), this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, global) { - - /** - * Utilities namespace. - * - * @namespace - */ - - var util = exports.util = {}; - - /** - * Parses an URI - * - * @author Steven Levithan (MIT license) - * @api public - */ - - var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - - var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', - 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', - 'anchor']; - - util.parseUri = function (str) { - var m = re.exec(str || '') - , uri = {} - , i = 14; - - while (i--) { - uri[parts[i]] = m[i] || ''; - } - - return uri; - }; - - /** - * Produces a unique url that identifies a Socket.IO connection. - * - * @param {Object} uri - * @api public - */ - - util.uniqueUri = function (uri) { - var protocol = uri.protocol - , host = uri.host - , port = uri.port; - - if ('document' in global) { - host = host || document.domain; - port = port || (protocol == 'https' - && document.location.protocol !== 'https:' ? 443 : document.location.port); - } else { - host = host || 'localhost'; - - if (!port && protocol == 'https') { - port = 443; - } - } - - return (protocol || 'http') + '://' + host + ':' + (port || 80); - }; - - /** - * Mergest 2 query strings in to once unique query string - * - * @param {String} base - * @param {String} addition - * @api public - */ - - util.query = function (base, addition) { - var query = util.chunkQuery(base || '') - , components = []; - - util.merge(query, util.chunkQuery(addition || '')); - for (var part in query) { - if (query.hasOwnProperty(part)) { - components.push(part + '=' + query[part]); - } - } - - return components.length ? '?' + components.join('&') : ''; - }; - - /** - * Transforms a querystring in to an object - * - * @param {String} qs - * @api public - */ - - util.chunkQuery = function (qs) { - var query = {} - , params = qs.split('&') - , i = 0 - , l = params.length - , kv; - - for (; i < l; ++i) { - kv = params[i].split('='); - if (kv[0]) { - query[kv[0]] = kv[1]; - } - } - - return query; - }; - - /** - * Executes the given function when the page is loaded. - * - * io.util.load(function () { console.log('page loaded'); }); - * - * @param {Function} fn - * @api public - */ - - var pageLoaded = false; - - util.load = function (fn) { - if ('document' in global && document.readyState === 'complete' || pageLoaded) { - return fn(); - } - - util.on(global, 'load', fn, false); - }; - - /** - * Adds an event. - * - * @api private - */ - - util.on = function (element, event, fn, capture) { - if (element.attachEvent) { - element.attachEvent('on' + event, fn); - } else if (element.addEventListener) { - element.addEventListener(event, fn, capture); - } - }; - - /** - * Generates the correct `XMLHttpRequest` for regular and cross domain requests. - * - * @param {Boolean} [xdomain] Create a request that can be used cross domain. - * @returns {XMLHttpRequest|false} If we can create a XMLHttpRequest. - * @api private - */ - - util.request = function (xdomain) { - - if (xdomain && 'undefined' != typeof XDomainRequest && !util.ua.hasCORS) { - return new XDomainRequest(); - } - - if ('undefined' != typeof XMLHttpRequest && (!xdomain || util.ua.hasCORS)) { - return new XMLHttpRequest(); - } - - if (!xdomain) { - try { - return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); - } catch(e) { } - } - - return null; - }; - - /** - * XHR based transport constructor. - * - * @constructor - * @api public - */ - - /** - * Change the internal pageLoaded value. - */ - - if ('undefined' != typeof window) { - util.load(function () { - pageLoaded = true; - }); - } - - /** - * Defers a function to ensure a spinner is not displayed by the browser - * - * @param {Function} fn - * @api public - */ - - util.defer = function (fn) { - if (!util.ua.webkit || 'undefined' != typeof importScripts) { - return fn(); - } - - util.load(function () { - setTimeout(fn, 100); - }); - }; - - /** - * Merges two objects. - * - * @api public - */ - - util.merge = function merge (target, additional, deep, lastseen) { - var seen = lastseen || [] - , depth = typeof deep == 'undefined' ? 2 : deep - , prop; - - for (prop in additional) { - if (additional.hasOwnProperty(prop) && util.indexOf(seen, prop) < 0) { - if (typeof target[prop] !== 'object' || !depth) { - target[prop] = additional[prop]; - seen.push(additional[prop]); - } else { - util.merge(target[prop], additional[prop], depth - 1, seen); - } - } - } - - return target; - }; - - /** - * Merges prototypes from objects - * - * @api public - */ - - util.mixin = function (ctor, ctor2) { - util.merge(ctor.prototype, ctor2.prototype); - }; - - /** - * Shortcut for prototypical and static inheritance. - * - * @api private - */ - - util.inherit = function (ctor, ctor2) { - function f() {}; - f.prototype = ctor2.prototype; - ctor.prototype = new f; - }; - - /** - * Checks if the given object is an Array. - * - * io.util.isArray([]); // true - * io.util.isArray({}); // false - * - * @param Object obj - * @api public - */ - - util.isArray = Array.isArray || function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - - /** - * Intersects values of two arrays into a third - * - * @api public - */ - - util.intersect = function (arr, arr2) { - var ret = [] - , longest = arr.length > arr2.length ? arr : arr2 - , shortest = arr.length > arr2.length ? arr2 : arr; - - for (var i = 0, l = shortest.length; i < l; i++) { - if (~util.indexOf(longest, shortest[i])) - ret.push(shortest[i]); - } - - return ret; - }; - - /** - * Array indexOf compatibility. - * - * @see bit.ly/a5Dxa2 - * @api public - */ - - util.indexOf = function (arr, o, i) { - - for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0; - i < j && arr[i] !== o; i++) {} - - return j <= i ? -1 : i; - }; - - /** - * Converts enumerables to array. - * - * @api public - */ - - util.toArray = function (enu) { - var arr = []; - - for (var i = 0, l = enu.length; i < l; i++) - arr.push(enu[i]); - - return arr; - }; - - /** - * UA / engines detection namespace. - * - * @namespace - */ - - util.ua = {}; - - /** - * Whether the UA supports CORS for XHR. - * - * @api public - */ - - util.ua.hasCORS = 'undefined' != typeof XMLHttpRequest && (function () { - try { - var a = new XMLHttpRequest(); - } catch (e) { - return false; - } - - return a.withCredentials != undefined; - })(); - - /** - * Detect webkit. - * - * @api public - */ - - util.ua.webkit = 'undefined' != typeof navigator - && /webkit/i.test(navigator.userAgent); - - /** - * Detect iPad/iPhone/iPod. - * - * @api public - */ - - util.ua.iDevice = 'undefined' != typeof navigator - && /iPad|iPhone|iPod/i.test(navigator.userAgent); - -})('undefined' != typeof io ? io : module.exports, this); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.EventEmitter = EventEmitter; - - /** - * Event emitter constructor. - * - * @api public. - */ - - function EventEmitter () {}; - - /** - * Adds a listener - * - * @api public - */ - - EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (io.util.isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; - } - - return this; - }; - - EventEmitter.prototype.addListener = EventEmitter.prototype.on; - - /** - * Adds a volatile listener. - * - * @api public - */ - - EventEmitter.prototype.once = function (name, fn) { - var self = this; - - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; - - on.listener = fn; - this.on(name, on); - - return this; - }; - - /** - * Removes a listener. - * - * @api public - */ - - EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (io.util.isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } - } - - return this; - }; - - /** - * Removes all listeners for an event. - * - * @api public - */ - - EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; - }; - - /** - * Gets all listeners for a certain event. - * - * @api publci - */ - - EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!io.util.isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; - }; - - /** - * Emits an event. - * - * @api public - */ - - EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = Array.prototype.slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (io.util.isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; - } - - return true; - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -/** - * Based on JSON2 (http://www.JSON.org/js.html). - */ - -(function (exports, nativeJSON) { - "use strict"; - - // use native JSON if it's available - if (nativeJSON && nativeJSON.parse){ - return exports.JSON = { - parse: nativeJSON.parse - , stringify: nativeJSON.stringify - }; - } - - var JSON = exports.JSON = {}; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - function date(d, key) { - return isFinite(d.valueOf()) ? - d.getUTCFullYear() + '-' + - f(d.getUTCMonth() + 1) + '-' + - f(d.getUTCDate()) + 'T' + - f(d.getUTCHours()) + ':' + - f(d.getUTCMinutes()) + ':' + - f(d.getUTCSeconds()) + 'Z' : null; - }; - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value instanceof Date) { - value = date(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - -// If the JSON object does not yet have a parse method, give it one. - - JSON.parse = function (text, reviver) { - // The parse method takes a text and an optional reviver function, and returns - // a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - - // The walk method is used to recursively walk the resulting structure so - // that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - - // Parsing happens in four stages. In the first stage, we replace certain - // Unicode characters with escape sequences. JavaScript handles many characters - // incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - - // In the second stage, we run the text against regular expressions that look - // for non-JSON patterns. We are especially concerned with '()' and 'new' - // because they can cause invocation, and '=' because it can cause mutation. - // But just to be safe, we want to reject all unexpected forms. - - // We split the second stage into 4 regexp operations in order to work around - // crippling inefficiencies in IE's and Safari's regexp engines. First we - // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we - // replace all simple value tokens with ']' characters. Third, we delete all - // open brackets that follow a colon or comma or that begin the text. Finally, - // we look to see that the remaining characters are only whitespace or ']' or - // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - - // In the third stage we use the eval function to compile the text into a - // JavaScript structure. The '{' operator is subject to a syntactic ambiguity - // in JavaScript: it can begin a block or an object literal. We wrap the text - // in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - - // In the optional fourth stage, we recursively walk the new structure, passing - // each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - - // If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , typeof JSON !== 'undefined' ? JSON : undefined -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Parser namespace. - * - * @namespace - */ - - var parser = exports.parser = {}; - - /** - * Packet types. - */ - - var packets = parser.packets = [ - 'disconnect' - , 'connect' - , 'heartbeat' - , 'message' - , 'json' - , 'event' - , 'ack' - , 'error' - , 'noop' - ]; - - /** - * Errors reasons. - */ - - var reasons = parser.reasons = [ - 'transport not supported' - , 'client not handshaken' - , 'unauthorized' - ]; - - /** - * Errors advice. - */ - - var advice = parser.advice = [ - 'reconnect' - ]; - - /** - * Shortcuts. - */ - - var JSON = io.JSON - , indexOf = io.util.indexOf; - - /** - * Encodes a packet. - * - * @api private - */ - - parser.encodePacket = function (packet) { - var type = indexOf(packets, packet.type) - , id = packet.id || '' - , endpoint = packet.endpoint || '' - , ack = packet.ack - , data = null; - - switch (packet.type) { - case 'error': - var reason = packet.reason ? indexOf(reasons, packet.reason) : '' - , adv = packet.advice ? indexOf(advice, packet.advice) : ''; - - if (reason !== '' || adv !== '') - data = reason + (adv !== '' ? ('+' + adv) : ''); - - break; - - case 'message': - if (packet.data !== '') - data = packet.data; - break; - - case 'event': - var ev = { name: packet.name }; - - if (packet.args && packet.args.length) { - ev.args = packet.args; - } - - data = JSON.stringify(ev); - break; - - case 'json': - data = JSON.stringify(packet.data); - break; - - case 'connect': - if (packet.qs) - data = packet.qs; - break; - - case 'ack': - data = packet.ackId - + (packet.args && packet.args.length - ? '+' + JSON.stringify(packet.args) : ''); - break; - } - - // construct packet with required fragments - var encoded = [ - type - , id + (ack == 'data' ? '+' : '') - , endpoint - ]; - - // data fragment is optional - if (data !== null && data !== undefined) - encoded.push(data); - - return encoded.join(':'); - }; - - /** - * Encodes multiple messages (payload). - * - * @param {Array} messages - * @api private - */ - - parser.encodePayload = function (packets) { - var decoded = ''; - - if (packets.length == 1) - return packets[0]; - - for (var i = 0, l = packets.length; i < l; i++) { - var packet = packets[i]; - decoded += '\ufffd' + packet.length + '\ufffd' + packets[i]; - } - - return decoded; - }; - - /** - * Decodes a packet - * - * @api private - */ - - var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/; - - parser.decodePacket = function (data) { - var pieces = data.match(regexp); - - if (!pieces) return {}; - - var id = pieces[2] || '' - , data = pieces[5] || '' - , packet = { - type: packets[pieces[1]] - , endpoint: pieces[4] || '' - }; - - // whether we need to acknowledge the packet - if (id) { - packet.id = id; - if (pieces[3]) - packet.ack = 'data'; - else - packet.ack = true; - } - - // handle different packet types - switch (packet.type) { - case 'error': - var pieces = data.split('+'); - packet.reason = reasons[pieces[0]] || ''; - packet.advice = advice[pieces[1]] || ''; - break; - - case 'message': - packet.data = data || ''; - break; - - case 'event': - try { - var opts = JSON.parse(data); - packet.name = opts.name; - packet.args = opts.args; - } catch (e) { } - - packet.args = packet.args || []; - break; - - case 'json': - try { - packet.data = JSON.parse(data); - } catch (e) { } - break; - - case 'connect': - packet.qs = data || ''; - break; - - case 'ack': - var pieces = data.match(/^([0-9]+)(\+)?(.*)/); - if (pieces) { - packet.ackId = pieces[1]; - packet.args = []; - - if (pieces[3]) { - try { - packet.args = pieces[3] ? JSON.parse(pieces[3]) : []; - } catch (e) { } - } - } - break; - - case 'disconnect': - case 'heartbeat': - break; - }; - - return packet; - }; - - /** - * Decodes data payload. Detects multiple messages - * - * @return {Array} messages - * @api public - */ - - parser.decodePayload = function (data) { - // IE doesn't like data[i] for unicode chars, charAt works fine - if (data.charAt(0) == '\ufffd') { - var ret = []; - - for (var i = 1, length = ''; i < data.length; i++) { - if (data.charAt(i) == '\ufffd') { - ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length))); - i += Number(length) + 1; - length = ''; - } else { - length += data.charAt(i); - } - } - - return ret; - } else { - return [parser.decodePacket(data)]; - } - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.Transport = Transport; - - /** - * This is the transport template for all supported transport methods. - * - * @constructor - * @api public - */ - - function Transport (socket, sessid) { - this.socket = socket; - this.sessid = sessid; - }; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(Transport, io.EventEmitter); - - - /** - * Indicates whether heartbeats is enabled for this transport - * - * @api private - */ - - Transport.prototype.heartbeats = function () { - return true; - }; - - /** - * Handles the response from the server. When a new response is received - * it will automatically update the timeout, decode the message and - * forwards the response to the onMessage function for further processing. - * - * @param {String} data Response from the server. - * @api private - */ - - Transport.prototype.onData = function (data) { - this.clearCloseTimeout(); - - // If the connection in currently open (or in a reopening state) reset the close - // timeout since we have just received data. This check is necessary so - // that we don't reset the timeout on an explicitly disconnected connection. - if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) { - this.setCloseTimeout(); - } - - if (data !== '') { - // todo: we should only do decodePayload for xhr transports - var msgs = io.parser.decodePayload(data); - - if (msgs && msgs.length) { - for (var i = 0, l = msgs.length; i < l; i++) { - this.onPacket(msgs[i]); - } - } - } - - return this; - }; - - /** - * Handles packets. - * - * @api private - */ - - Transport.prototype.onPacket = function (packet) { - this.socket.setHeartbeatTimeout(); - - if (packet.type == 'heartbeat') { - return this.onHeartbeat(); - } - - if (packet.type == 'connect' && packet.endpoint == '') { - this.onConnect(); - } - - if (packet.type == 'error' && packet.advice == 'reconnect') { - this.isOpen = false; - } - - this.socket.onPacket(packet); - - return this; - }; - - /** - * Sets close timeout - * - * @api private - */ - - Transport.prototype.setCloseTimeout = function () { - if (!this.closeTimeout) { - var self = this; - - this.closeTimeout = setTimeout(function () { - self.onDisconnect(); - }, this.socket.closeTimeout); - } - }; - - /** - * Called when transport disconnects. - * - * @api private - */ - - Transport.prototype.onDisconnect = function () { - if (this.isOpen) this.close(); - this.clearTimeouts(); - this.socket.onDisconnect(); - return this; - }; - - /** - * Called when transport connects - * - * @api private - */ - - Transport.prototype.onConnect = function () { - this.socket.onConnect(); - return this; - }; - - /** - * Clears close timeout - * - * @api private - */ - - Transport.prototype.clearCloseTimeout = function () { - if (this.closeTimeout) { - clearTimeout(this.closeTimeout); - this.closeTimeout = null; - } - }; - - /** - * Clear timeouts - * - * @api private - */ - - Transport.prototype.clearTimeouts = function () { - this.clearCloseTimeout(); - - if (this.reopenTimeout) { - clearTimeout(this.reopenTimeout); - } - }; - - /** - * Sends a packet - * - * @param {Object} packet object. - * @api private - */ - - Transport.prototype.packet = function (packet) { - this.send(io.parser.encodePacket(packet)); - }; - - /** - * Send the received heartbeat message back to server. So the server - * knows we are still connected. - * - * @param {String} heartbeat Heartbeat response from the server. - * @api private - */ - - Transport.prototype.onHeartbeat = function (heartbeat) { - this.packet({ type: 'heartbeat' }); - }; - - /** - * Called when the transport opens. - * - * @api private - */ - - Transport.prototype.onOpen = function () { - this.isOpen = true; - this.clearCloseTimeout(); - this.socket.onOpen(); - }; - - /** - * Notifies the base when the connection with the Socket.IO server - * has been disconnected. - * - * @api private - */ - - Transport.prototype.onClose = function () { - var self = this; - - /* FIXME: reopen delay causing a infinit loop - this.reopenTimeout = setTimeout(function () { - self.open(); - }, this.socket.options['reopen delay']);*/ - - this.isOpen = false; - this.socket.onClose(); - this.onDisconnect(); - }; - - /** - * Generates a connection url based on the Socket.IO URL Protocol. - * See for more details. - * - * @returns {String} Connection url - * @api private - */ - - Transport.prototype.prepareUrl = function () { - var options = this.socket.options; - - return this.scheme() + '://' - + options.host + ':' + options.port + '/' - + options.resource + '/' + io.protocol - + '/' + this.name + '/' + this.sessid; - }; - - /** - * Checks if the transport is ready to start a connection. - * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback - * @api private - */ - - Transport.prototype.ready = function (socket, fn) { - fn.call(this); - }; -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports.Socket = Socket; - - /** - * Create a new `Socket.IO client` which can establish a persistent - * connection with a Socket.IO enabled server. - * - * @api public - */ - - function Socket (options) { - this.options = { - port: 80 - , secure: false - , document: 'document' in global ? document : false - , resource: 'socket.io' - , transports: io.transports - , 'connect timeout': 10000 - , 'try multiple transports': true - , 'reconnect': true - , 'reconnection delay': 500 - , 'reconnection limit': Infinity - , 'reopen delay': 3000 - , 'max reconnection attempts': 10 - , 'sync disconnect on unload': false - , 'auto connect': true - , 'flash policy port': 10843 - , 'manualFlush': false - }; - - io.util.merge(this.options, options); - - this.connected = false; - this.open = false; - this.connecting = false; - this.reconnecting = false; - this.namespaces = {}; - this.buffer = []; - this.doBuffer = false; - - if (this.options['sync disconnect on unload'] && - (!this.isXDomain() || io.util.ua.hasCORS)) { - var self = this; - io.util.on(global, 'beforeunload', function () { - self.disconnectSync(); - }, false); - } - - if (this.options['auto connect']) { - this.connect(); - } -}; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(Socket, io.EventEmitter); - - /** - * Returns a namespace listener/emitter for this socket - * - * @api public - */ - - Socket.prototype.of = function (name) { - if (!this.namespaces[name]) { - this.namespaces[name] = new io.SocketNamespace(this, name); - - if (name !== '') { - this.namespaces[name].packet({ type: 'connect' }); - } - } - - return this.namespaces[name]; - }; - - /** - * Emits the given event to the Socket and all namespaces - * - * @api private - */ - - Socket.prototype.publish = function () { - this.emit.apply(this, arguments); - - var nsp; - - for (var i in this.namespaces) { - if (this.namespaces.hasOwnProperty(i)) { - nsp = this.of(i); - nsp.$emit.apply(nsp, arguments); - } - } - }; - - /** - * Performs the handshake - * - * @api private - */ - - function empty () { }; - - Socket.prototype.handshake = function (fn) { - var self = this - , options = this.options; - - function complete (data) { - if (data instanceof Error) { - self.connecting = false; - self.onError(data.message); - } else { - fn.apply(null, data.split(':')); - } - }; - - var url = [ - 'http' + (options.secure ? 's' : '') + ':/' - , options.host + ':' + options.port - , options.resource - , io.protocol - , io.util.query(this.options.query, 't=' + +new Date) - ].join('/'); - - if (this.isXDomain() && !io.util.ua.hasCORS) { - var insertAt = document.getElementsByTagName('script')[0] - , script = document.createElement('script'); - - script.src = url + '&jsonp=' + io.j.length; - insertAt.parentNode.insertBefore(script, insertAt); - - io.j.push(function (data) { - complete(data); - script.parentNode.removeChild(script); - }); - } else { - var xhr = io.util.request(); - - xhr.open('GET', url, true); - if (this.isXDomain()) { - xhr.withCredentials = true; - } - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - xhr.onreadystatechange = empty; - - if (xhr.status == 200) { - complete(xhr.responseText); - } else if (xhr.status == 403) { - self.onError(xhr.responseText); - } else { - self.connecting = false; - !self.reconnecting && self.onError(xhr.responseText); - } - } - }; - xhr.send(null); - } - }; - - /** - * Find an available transport based on the options supplied in the constructor. - * - * @api private - */ - - Socket.prototype.getTransport = function (override) { - var transports = override || this.transports, match; - - for (var i = 0, transport; transport = transports[i]; i++) { - if (io.Transport[transport] - && io.Transport[transport].check(this) - && (!this.isXDomain() || io.Transport[transport].xdomainCheck(this))) { - return new io.Transport[transport](this, this.sessionid); - } - } - - return null; - }; - - /** - * Connects to the server. - * - * @param {Function} [fn] Callback. - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.connect = function (fn) { - if (this.connecting) { - return this; - } - - var self = this; - self.connecting = true; - - this.handshake(function (sid, heartbeat, close, transports) { - self.sessionid = sid; - self.closeTimeout = close * 1000; - self.heartbeatTimeout = heartbeat * 1000; - if(!self.transports) - self.transports = self.origTransports = (transports ? io.util.intersect( - transports.split(',') - , self.options.transports - ) : self.options.transports); - - self.setHeartbeatTimeout(); - - function connect (transports){ - if (self.transport) self.transport.clearTimeouts(); - - self.transport = self.getTransport(transports); - if (!self.transport) return self.publish('connect_failed'); - - // once the transport is ready - self.transport.ready(self, function () { - self.connecting = true; - self.publish('connecting', self.transport.name); - self.transport.open(); - - if (self.options['connect timeout']) { - self.connectTimeoutTimer = setTimeout(function () { - if (!self.connected) { - self.connecting = false; - - if (self.options['try multiple transports']) { - var remaining = self.transports; - - while (remaining.length > 0 && remaining.splice(0,1)[0] != - self.transport.name) {} - - if (remaining.length){ - connect(remaining); - } else { - self.publish('connect_failed'); - } - } - } - }, self.options['connect timeout']); - } - }); - } - - connect(self.transports); - - self.once('connect', function (){ - clearTimeout(self.connectTimeoutTimer); - - fn && typeof fn == 'function' && fn(); - }); - }); - - return this; - }; - - /** - * Clears and sets a new heartbeat timeout using the value given by the - * server during the handshake. - * - * @api private - */ - - Socket.prototype.setHeartbeatTimeout = function () { - clearTimeout(this.heartbeatTimeoutTimer); - if(this.transport && !this.transport.heartbeats()) return; - - var self = this; - this.heartbeatTimeoutTimer = setTimeout(function () { - self.transport.onClose(); - }, this.heartbeatTimeout); - }; - - /** - * Sends a message. - * - * @param {Object} data packet. - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.packet = function (data) { - if (this.connected && !this.doBuffer) { - this.transport.packet(data); - } else { - this.buffer.push(data); - } - - return this; - }; - - /** - * Sets buffer state - * - * @api private - */ - - Socket.prototype.setBuffer = function (v) { - this.doBuffer = v; - - if (!v && this.connected && this.buffer.length) { - if (!this.options['manualFlush']) { - this.flushBuffer(); - } - } - }; - - /** - * Flushes the buffer data over the wire. - * To be invoked manually when 'manualFlush' is set to true. - * - * @api public - */ - - Socket.prototype.flushBuffer = function() { - this.transport.payload(this.buffer); - this.buffer = []; - }; - - - /** - * Disconnect the established connect. - * - * @returns {io.Socket} - * @api public - */ - - Socket.prototype.disconnect = function () { - if (this.connected || this.connecting) { - if (this.open) { - this.of('').packet({ type: 'disconnect' }); - } - - // handle disconnection immediately - this.onDisconnect('booted'); - } - - return this; - }; - - /** - * Disconnects the socket with a sync XHR. - * - * @api private - */ - - Socket.prototype.disconnectSync = function () { - // ensure disconnection - var xhr = io.util.request(); - var uri = [ - 'http' + (this.options.secure ? 's' : '') + ':/' - , this.options.host + ':' + this.options.port - , this.options.resource - , io.protocol - , '' - , this.sessionid - ].join('/') + '/?disconnect=1'; - - xhr.open('GET', uri, false); - xhr.send(null); - - // handle disconnection immediately - this.onDisconnect('booted'); - }; - - /** - * Check if we need to use cross domain enabled transports. Cross domain would - * be a different port or different domain name. - * - * @returns {Boolean} - * @api private - */ - - Socket.prototype.isXDomain = function () { - - var port = global.location.port || - ('https:' == global.location.protocol ? 443 : 80); - - return this.options.host !== global.location.hostname - || this.options.port != port; - }; - - /** - * Called upon handshake. - * - * @api private - */ - - Socket.prototype.onConnect = function () { - if (!this.connected) { - this.connected = true; - this.connecting = false; - if (!this.doBuffer) { - // make sure to flush the buffer - this.setBuffer(false); - } - this.emit('connect'); - } - }; - - /** - * Called when the transport opens - * - * @api private - */ - - Socket.prototype.onOpen = function () { - this.open = true; - }; - - /** - * Called when the transport closes. - * - * @api private - */ - - Socket.prototype.onClose = function () { - this.open = false; - clearTimeout(this.heartbeatTimeoutTimer); - }; - - /** - * Called when the transport first opens a connection - * - * @param text - */ - - Socket.prototype.onPacket = function (packet) { - this.of(packet.endpoint).onPacket(packet); - }; - - /** - * Handles an error. - * - * @api private - */ - - Socket.prototype.onError = function (err) { - if (err && err.advice) { - if (err.advice === 'reconnect' && (this.connected || this.connecting)) { - this.disconnect(); - if (this.options.reconnect) { - this.reconnect(); - } - } - } - - this.publish('error', err && err.reason ? err.reason : err); - }; - - /** - * Called when the transport disconnects. - * - * @api private - */ - - Socket.prototype.onDisconnect = function (reason) { - var wasConnected = this.connected - , wasConnecting = this.connecting; - - this.connected = false; - this.connecting = false; - this.open = false; - - if (wasConnected || wasConnecting) { - this.transport.close(); - this.transport.clearTimeouts(); - if (wasConnected) { - this.publish('disconnect', reason); - - if ('booted' != reason && this.options.reconnect && !this.reconnecting) { - this.reconnect(); - } - } - } - }; - - /** - * Called upon reconnection. - * - * @api private - */ - - Socket.prototype.reconnect = function () { - this.reconnecting = true; - this.reconnectionAttempts = 0; - this.reconnectionDelay = this.options['reconnection delay']; - - var self = this - , maxAttempts = this.options['max reconnection attempts'] - , tryMultiple = this.options['try multiple transports'] - , limit = this.options['reconnection limit']; - - function reset () { - if (self.connected) { - for (var i in self.namespaces) { - if (self.namespaces.hasOwnProperty(i) && '' !== i) { - self.namespaces[i].packet({ type: 'connect' }); - } - } - self.publish('reconnect', self.transport.name, self.reconnectionAttempts); - } - - clearTimeout(self.reconnectionTimer); - - self.removeListener('connect_failed', maybeReconnect); - self.removeListener('connect', maybeReconnect); - - self.reconnecting = false; - - delete self.reconnectionAttempts; - delete self.reconnectionDelay; - delete self.reconnectionTimer; - delete self.redoTransports; - - self.options['try multiple transports'] = tryMultiple; - }; - - function maybeReconnect () { - if (!self.reconnecting) { - return; - } - - if (self.connected) { - return reset(); - }; - - if (self.connecting && self.reconnecting) { - return self.reconnectionTimer = setTimeout(maybeReconnect, 1000); - } - - if (self.reconnectionAttempts++ >= maxAttempts) { - if (!self.redoTransports) { - self.on('connect_failed', maybeReconnect); - self.options['try multiple transports'] = true; - self.transports = self.origTransports; - self.transport = self.getTransport(); - self.redoTransports = true; - self.connect(); - } else { - self.publish('reconnect_failed'); - reset(); - } - } else { - if (self.reconnectionDelay < limit) { - self.reconnectionDelay *= 2; // exponential back off - } - - self.connect(); - self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts); - self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay); - } - }; - - this.options['try multiple transports'] = false; - this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay); - - this.on('connect', maybeReconnect); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io) { - - /** - * Expose constructor. - */ - - exports.SocketNamespace = SocketNamespace; - - /** - * Socket namespace constructor. - * - * @constructor - * @api public - */ - - function SocketNamespace (socket, name) { - this.socket = socket; - this.name = name || ''; - this.flags = {}; - this.json = new Flag(this, 'json'); - this.ackPackets = 0; - this.acks = {}; - }; - - /** - * Apply EventEmitter mixin. - */ - - io.util.mixin(SocketNamespace, io.EventEmitter); - - /** - * Copies emit since we override it - * - * @api private - */ - - SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit; - - /** - * Creates a new namespace, by proxying the request to the socket. This - * allows us to use the synax as we do on the server. - * - * @api public - */ - - SocketNamespace.prototype.of = function () { - return this.socket.of.apply(this.socket, arguments); - }; - - /** - * Sends a packet. - * - * @api private - */ - - SocketNamespace.prototype.packet = function (packet) { - packet.endpoint = this.name; - this.socket.packet(packet); - this.flags = {}; - return this; - }; - - /** - * Sends a message - * - * @api public - */ - - SocketNamespace.prototype.send = function (data, fn) { - var packet = { - type: this.flags.json ? 'json' : 'message' - , data: data - }; - - if ('function' == typeof fn) { - packet.id = ++this.ackPackets; - packet.ack = true; - this.acks[packet.id] = fn; - } - - return this.packet(packet); - }; - - /** - * Emits an event - * - * @api public - */ - - SocketNamespace.prototype.emit = function (name) { - var args = Array.prototype.slice.call(arguments, 1) - , lastArg = args[args.length - 1] - , packet = { - type: 'event' - , name: name - }; - - if ('function' == typeof lastArg) { - packet.id = ++this.ackPackets; - packet.ack = 'data'; - this.acks[packet.id] = lastArg; - args = args.slice(0, args.length - 1); - } - - packet.args = args; - - return this.packet(packet); - }; - - /** - * Disconnects the namespace - * - * @api private - */ - - SocketNamespace.prototype.disconnect = function () { - if (this.name === '') { - this.socket.disconnect(); - } else { - this.packet({ type: 'disconnect' }); - this.$emit('disconnect'); - } - - return this; - }; - - /** - * Handles a packet - * - * @api private - */ - - SocketNamespace.prototype.onPacket = function (packet) { - var self = this; - - function ack () { - self.packet({ - type: 'ack' - , args: io.util.toArray(arguments) - , ackId: packet.id - }); - }; - - switch (packet.type) { - case 'connect': - this.$emit('connect'); - break; - - case 'disconnect': - if (this.name === '') { - this.socket.onDisconnect(packet.reason || 'booted'); - } else { - this.$emit('disconnect', packet.reason); - } - break; - - case 'message': - case 'json': - var params = ['message', packet.data]; - - if (packet.ack == 'data') { - params.push(ack); - } else if (packet.ack) { - this.packet({ type: 'ack', ackId: packet.id }); - } - - this.$emit.apply(this, params); - break; - - case 'event': - var params = [packet.name].concat(packet.args); - - if (packet.ack == 'data') - params.push(ack); - - this.$emit.apply(this, params); - break; - - case 'ack': - if (this.acks[packet.ackId]) { - this.acks[packet.ackId].apply(this, packet.args); - delete this.acks[packet.ackId]; - } - break; - - case 'error': - if (packet.advice){ - this.socket.onError(packet); - } else { - if (packet.reason == 'unauthorized') { - this.$emit('connect_failed', packet.reason); - } else { - this.$emit('error', packet.reason); - } - } - break; - } - }; - - /** - * Flag interface. - * - * @api private - */ - - function Flag (nsp, name) { - this.namespace = nsp; - this.name = name; - }; - - /** - * Send a message - * - * @api public - */ - - Flag.prototype.send = function () { - this.namespace.flags[this.name] = true; - this.namespace.send.apply(this.namespace, arguments); - }; - - /** - * Emit an event - * - * @api public - */ - - Flag.prototype.emit = function () { - this.namespace.flags[this.name] = true; - this.namespace.emit.apply(this.namespace, arguments); - }; - -})( - 'undefined' != typeof io ? io : module.exports - , 'undefined' != typeof io ? io : module.parent.exports -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports.websocket = WS; - - /** - * The WebSocket transport uses the HTML5 WebSocket API to establish an - * persistent connection with the Socket.IO server. This transport will also - * be inherited by the FlashSocket fallback as it provides a API compatible - * polyfill for the WebSockets. - * - * @constructor - * @extends {io.Transport} - * @api public - */ - - function WS (socket) { - io.Transport.apply(this, arguments); - }; - - /** - * Inherits from Transport. - */ - - io.util.inherit(WS, io.Transport); - - /** - * Transport name - * - * @api public - */ - - WS.prototype.name = 'websocket'; - - /** - * Initializes a new `WebSocket` connection with the Socket.IO server. We attach - * all the appropriate listeners to handle the responses from the server. - * - * @returns {Transport} - * @api public - */ - - WS.prototype.open = function () { - var query = io.util.query(this.socket.options.query) - , self = this - , Socket - - - if (!Socket) { - Socket = global.MozWebSocket || global.WebSocket; - } - - this.websocket = new Socket(this.prepareUrl() + query); - - this.websocket.onopen = function () { - self.onOpen(); - self.socket.setBuffer(false); - }; - this.websocket.onmessage = function (ev) { - self.onData(ev.data); - }; - this.websocket.onclose = function () { - self.onClose(); - self.socket.setBuffer(true); - }; - this.websocket.onerror = function (e) { - self.onError(e); - }; - - return this; - }; - - /** - * Send a message to the Socket.IO server. The message will automatically be - * encoded in the correct message format. - * - * @returns {Transport} - * @api public - */ - - // Do to a bug in the current IDevices browser, we need to wrap the send in a - // setTimeout, when they resume from sleeping the browser will crash if - // we don't allow the browser time to detect the socket has been closed - if (io.util.ua.iDevice) { - WS.prototype.send = function (data) { - var self = this; - setTimeout(function() { - self.websocket.send(data); - },0); - return this; - }; - } else { - WS.prototype.send = function (data) { - this.websocket.send(data); - return this; - }; - } - - /** - * Payload - * - * @api private - */ - - WS.prototype.payload = function (arr) { - for (var i = 0, l = arr.length; i < l; i++) { - this.packet(arr[i]); - } - return this; - }; - - /** - * Disconnect the established `WebSocket` connection. - * - * @returns {Transport} - * @api public - */ - - WS.prototype.close = function () { - this.websocket.close(); - return this; - }; - - /** - * Handle the errors that `WebSocket` might be giving when we - * are attempting to connect or send messages. - * - * @param {Error} e The error. - * @api private - */ - - WS.prototype.onError = function (e) { - this.socket.onError(e); - }; - - /** - * Returns the appropriate scheme for the URI generation. - * - * @api private - */ - WS.prototype.scheme = function () { - return this.socket.options.secure ? 'wss' : 'ws'; - }; - - /** - * Checks if the browser has support for native `WebSockets` and that - * it's not the polyfill created for the FlashSocket transport. - * - * @return {Boolean} - * @api public - */ - - WS.check = function () { - return ('WebSocket' in global && !('__addTask' in WebSocket)) - || 'MozWebSocket' in global; - }; - - /** - * Check if the `WebSocket` transport support cross domain communications. - * - * @returns {Boolean} - * @api public - */ - - WS.xdomainCheck = function () { - return true; - }; - - /** - * Add the transport to your public io.transports array. - * - * @api private - */ - - io.transports.push('websocket'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - * - * @api public - */ - - exports.XHR = XHR; - - /** - * XHR constructor - * - * @costructor - * @api public - */ - - function XHR (socket) { - if (!socket) return; - - io.Transport.apply(this, arguments); - this.sendBuffer = []; - }; - - /** - * Inherits from Transport. - */ - - io.util.inherit(XHR, io.Transport); - - /** - * Establish a connection - * - * @returns {Transport} - * @api public - */ - - XHR.prototype.open = function () { - this.socket.setBuffer(false); - this.onOpen(); - this.get(); - - // we need to make sure the request succeeds since we have no indication - // whether the request opened or not until it succeeded. - this.setCloseTimeout(); - - return this; - }; - - /** - * Check if we need to send data to the Socket.IO server, if we have data in our - * buffer we encode it and forward it to the `post` method. - * - * @api private - */ - - XHR.prototype.payload = function (payload) { - var msgs = []; - - for (var i = 0, l = payload.length; i < l; i++) { - msgs.push(io.parser.encodePacket(payload[i])); - } - - this.send(io.parser.encodePayload(msgs)); - }; - - /** - * Send data to the Socket.IO server. - * - * @param data The message - * @returns {Transport} - * @api public - */ - - XHR.prototype.send = function (data) { - this.post(data); - return this; - }; - - /** - * Posts a encoded message to the Socket.IO server. - * - * @param {String} data A encoded message. - * @api private - */ - - function empty () { }; - - XHR.prototype.post = function (data) { - var self = this; - this.socket.setBuffer(true); - - function stateChange () { - if (this.readyState == 4) { - this.onreadystatechange = empty; - self.posting = false; - - if (this.status == 200){ - self.socket.setBuffer(false); - } else { - self.onClose(); - } - } - } - - function onload () { - this.onload = empty; - self.socket.setBuffer(false); - }; - - this.sendXHR = this.request('POST'); - - if (global.XDomainRequest && this.sendXHR instanceof XDomainRequest) { - this.sendXHR.onload = this.sendXHR.onerror = onload; - } else { - this.sendXHR.onreadystatechange = stateChange; - } - - this.sendXHR.send(data); - }; - - /** - * Disconnects the established `XHR` connection. - * - * @returns {Transport} - * @api public - */ - - XHR.prototype.close = function () { - this.onClose(); - return this; - }; - - /** - * Generates a configured XHR request - * - * @param {String} url The url that needs to be requested. - * @param {String} method The method the request should use. - * @returns {XMLHttpRequest} - * @api private - */ - - XHR.prototype.request = function (method) { - var req = io.util.request(this.socket.isXDomain()) - , query = io.util.query(this.socket.options.query, 't=' + +new Date); - - req.open(method || 'GET', this.prepareUrl() + query, true); - - if (method == 'POST') { - try { - if (req.setRequestHeader) { - req.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); - } else { - // XDomainRequest - req.contentType = 'text/plain'; - } - } catch (e) {} - } - - return req; - }; - - /** - * Returns the scheme to use for the transport URLs. - * - * @api private - */ - - XHR.prototype.scheme = function () { - return this.socket.options.secure ? 'https' : 'http'; - }; - - /** - * Check if the XHR transports are supported - * - * @param {Boolean} xdomain Check if we support cross domain requests. - * @returns {Boolean} - * @api public - */ - - XHR.check = function (socket, xdomain) { - try { - var request = io.util.request(xdomain), - usesXDomReq = (global.XDomainRequest && request instanceof XDomainRequest), - socketProtocol = (socket && socket.options && socket.options.secure ? 'https:' : 'http:'), - isXProtocol = (global.location && socketProtocol != global.location.protocol); - if (request && !(usesXDomReq && isXProtocol)) { - return true; - } - } catch(e) {} - - return false; - }; - - /** - * Check if the XHR transport supports cross domain requests. - * - * @returns {Boolean} - * @api public - */ - - XHR.xdomainCheck = function (socket) { - return XHR.check(socket, true); - }; - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - - /** - * Expose constructor. - */ - - exports['xhr-polling'] = XHRPolling; - - /** - * The XHR-polling transport uses long polling XHR requests to create a - * "persistent" connection with the server. - * - * @constructor - * @api public - */ - - function XHRPolling () { - io.Transport.XHR.apply(this, arguments); - }; - - /** - * Inherits from XHR transport. - */ - - io.util.inherit(XHRPolling, io.Transport.XHR); - - /** - * Merge the properties from XHR transport - */ - - io.util.merge(XHRPolling, io.Transport.XHR); - - /** - * Transport name - * - * @api public - */ - - XHRPolling.prototype.name = 'xhr-polling'; - - /** - * Indicates whether heartbeats is enabled for this transport - * - * @api private - */ - - XHRPolling.prototype.heartbeats = function () { - return false; - }; - - /** - * Establish a connection, for iPhone and Android this will be done once the page - * is loaded. - * - * @returns {Transport} Chaining. - * @api public - */ - - XHRPolling.prototype.open = function () { - var self = this; - - io.Transport.XHR.prototype.open.call(self); - return false; - }; - - /** - * Starts a XHR request to wait for incoming messages. - * - * @api private - */ - - function empty () {}; - - XHRPolling.prototype.get = function () { - if (!this.isOpen) return; - - var self = this; - - function stateChange () { - if (this.readyState == 4) { - this.onreadystatechange = empty; - - if (this.status == 200) { - self.onData(this.responseText); - self.get(); - } else { - self.onClose(); - } - } - }; - - function onload () { - this.onload = empty; - this.onerror = empty; - self.retryCounter = 1; - self.onData(this.responseText); - self.get(); - }; - - function onerror () { - self.retryCounter ++; - if(!self.retryCounter || self.retryCounter > 3) { - self.onClose(); - } else { - self.get(); - } - }; - - this.xhr = this.request(); - - if (global.XDomainRequest && this.xhr instanceof XDomainRequest) { - this.xhr.onload = onload; - this.xhr.onerror = onerror; - } else { - this.xhr.onreadystatechange = stateChange; - } - - this.xhr.send(null); - }; - - /** - * Handle the unclean close behavior. - * - * @api private - */ - - XHRPolling.prototype.onClose = function () { - io.Transport.XHR.prototype.onClose.call(this); - - if (this.xhr) { - this.xhr.onreadystatechange = this.xhr.onload = this.xhr.onerror = empty; - try { - this.xhr.abort(); - } catch(e){} - this.xhr = null; - } - }; - - /** - * Webkit based browsers show a infinit spinner when you start a XHR request - * before the browsers onload event is called so we need to defer opening of - * the transport until the onload event is called. Wrapping the cb in our - * defer method solve this. - * - * @param {Socket} socket The socket instance that needs a transport - * @param {Function} fn The callback - * @api private - */ - - XHRPolling.prototype.ready = function (socket, fn) { - var self = this; - - io.util.defer(function () { - fn.call(self); - }); - }; - - /** - * Add the transport to your public io.transports array. - * - * @api private - */ - - io.transports.push('xhr-polling'); - -})( - 'undefined' != typeof io ? io.Transport : module.exports - , 'undefined' != typeof io ? io : module.parent.exports - , this -); - -/** - * socket.io - * Copyright(c) 2011 LearnBoost - * MIT Licensed - */ - -(function (exports, io, global) { - /** - * There is a way to hide the loading indicator in Firefox. If you create and - * remove a iframe it will stop showing the current loading indicator. - * Unfortunately we can't feature detect that and UA sniffing is evil. - * - * @api private - */ - - var indicator = global.document && "MozAppearance" in - global.document.documentElement.style; - - /** - * Expose constructor. - */ - - exports['jsonp-polling'] = JSONPPolling; - - /** - * The JSONP transport creates an persistent connection by dynamically - * inserting a script tag in the page. This script tag will receive the - * information of the Socket.IO server. When new information is received - * it creates a new script tag for the new data stream. - * - * @constructor - * @extends {io.Transport.xhr-polling} - * @api public - */ - - function JSONPPolling (socket) { - io.Transport['xhr-polling'].apply(this, arguments); - - this.index = io.j.length; - - var self = this; - - io.j.push(function (msg) { - self._(msg); - }); - }; - - /** - * Inherits from XHR polling transport. - */ - - io.util.inherit(JSONPPolling, io.Transport['xhr-polling']); - - /** - * Transport name - * - * @api public - */ - - JSONPPolling.prototype.name = 'jsonp-polling'; - - /** - * Posts a encoded message to the Socket.IO server using an iframe. - * The iframe is used because script tags can create POST based requests. - * The iframe is positioned outside of the view so the user does not - * notice it's existence. - * - * @param {String} data A encoded message. - * @api private - */ - - JSONPPolling.prototype.post = function (data) { - var self = this - , query = io.util.query( - this.socket.options.query - , 't='+ (+new Date) + '&i=' + this.index - ); - - if (!this.form) { - var form = document.createElement('form') - , area = document.createElement('textarea') - , id = this.iframeId = 'socketio_iframe_' + this.index - , iframe; - - form.className = 'socketio'; - form.style.position = 'absolute'; - form.style.top = '0px'; - form.style.left = '0px'; - form.style.display = 'none'; - form.target = id; - form.method = 'POST'; - form.setAttribute('accept-charset', 'utf-8'); - area.name = 'd'; - form.appendChild(area); - document.body.appendChild(form); - - this.form = form; - this.area = area; - } - - this.form.action = this.prepareUrl() + query; - - function complete () { - initIframe(); - self.socket.setBuffer(false); - }; - - function initIframe () { - if (self.iframe) { - self.form.removeChild(self.iframe); - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - iframe = document.createElement(' + throw 'NOTE: you need to have an iframe in the page right above the script tag.'; + } + var win = iframe.contentWindow; + RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection; + useWebKit = !!win.webkitRTCPeerConnection; + } - channel.onerror = function(error) { - config.onDataChannelError(error); - }; + // if still no RTCPeerConnection then it is not supported by the browser so just return + if (!RTCPeerConnection) { + return; + } - channel.onclose = function(event) { - config.onDataChannelClosed(event); + //minimal requirements for data connection + var mediaConstraints = { + optional: [{ + RtpDataChannels: true + }] }; - channel.internalSend = channel.send; - channel.send = function(data) { - if (channel.readyState !== 'open') { + //firefox already has a default stun server in about:config + // media.peerconnection.default_iceservers = + // [{"url": "stun:stun.services.mozilla.com"}] + var servers; + + //add same stun server for chrome + if (useWebKit) { + servers = { + iceServers: [{ + urls: 'stun:stun.services.mozilla.com' + }] + }; + + if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isFirefox && DetectRTC.browser.version <= 38) { + servers[0] = { + url: servers[0].urls + }; + } + } + + //construct a new RTCPeerConnection + var pc = new RTCPeerConnection(servers, mediaConstraints); + + function handleCandidate(candidate) { + //match just the IP address + var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; + var match = ipRegex.exec(candidate); + if (!match) { + console.warn('Could not match IP address in', candidate); return; } + var ipAddress = match[1]; - channel.internalSend(data); + //remove duplicates + if (ipDuplicates[ipAddress] === undefined) { + callback(ipAddress); + } + + ipDuplicates[ipAddress] = true; + } + + //listen for candidate events + pc.onicecandidate = function(ice) { + //skip non-candidate events + if (ice.candidate) { + handleCandidate(ice.candidate.candidate); + } }; - peer.channel = channel; - } + //create a bogus data channel + pc.createDataChannel(''); - if (config.session.audio == 'two-way' || config.session.video == 'two-way' || config.session.screen == 'two-way') { - defaults.sdpConstraints = setSdpConstraints({ - OfferToReceiveAudio: config.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), - OfferToReceiveVideo: config.session.video == 'two-way' || config.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) - }); - } + //create an offer sdp + pc.createOffer(function(result) { - var streamsToShare = {}; - peer.getLocalStreams().forEach(function(stream) { - streamsToShare[stream.streamid] = { - isAudio: !!stream.isAudio, - isVideo: !!stream.isVideo, - isScreen: !!stream.isScreen - }; - }); + //trigger the stun server request + pc.setLocalDescription(result, function() {}, function() {}); - peer[isOfferer ? 'createOffer' : 'createAnswer'](function(localSdp) { - localSdp.sdp = config.processSdp(localSdp.sdp); - peer.setLocalDescription(localSdp); - config.onLocalSdp({ - type: localSdp.type, - sdp: localSdp.sdp, - remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, - renegotiatingPeer: !!config.renegotiatingPeer || false, - connectionDescription: that.connectionDescription, - dontGetRemoteStream: !!config.dontGetRemoteStream, - extra: config.rtcMultiConnection ? config.rtcMultiConnection.extra : {}, - streamsToShare: streamsToShare, - isFirefoxOffered: isFirefox - }); - }, function(error) { - if (!!config.rtcMultiConnection.enableLogs) { - console.error('sdp-error', error); - } + }, function() {}); - if (!!config.rtcMultiConnection.autoReDialOnFailure && !isFirefox && !isFirefoxOffered) { - setTimeout(function() { - config.rtcMultiConnection.rejoin(that.connectionDescription); - }, 2000); - } - }, defaults.sdpConstraints); + //wait for a while to let everything done + setTimeout(function() { + //read candidate info from local description + var lines = pc.localDescription.sdp.split('\n'); - peer.nativeClose = peer.close; - peer.close = function() { - if (peer && peer.iceConnectionState === 'connected') { - peer.nativeClose(); - peer = null; - } - }; + lines.forEach(function(line) { + if (line.indexOf('a=candidate:') === 0) { + handleCandidate(line); + } + }); + }, 1000); + } - this.peer = peer; - } + var MediaDevices = []; - // OnIceCandidateHandler.js + var audioInputDevices = []; + var audioOutputDevices = []; + var videoInputDevices = []; - var OnIceCandidateHandler = (function() { - function processCandidates(connection, icePair) { - var candidate = icePair.candidate; + if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { + // Firefox 38+ seems having support of enumerateDevices + // Thanks @xdumaine/enumerateDevices + navigator.enumerateDevices = function(callback) { + navigator.mediaDevices.enumerateDevices().then(callback); + }; + } - var iceRestrictions = connection.candidates; - var stun = iceRestrictions.stun; - var turn = iceRestrictions.turn; + // ---------- Media Devices detection + var canEnumerate = false; - if (!isNull(iceRestrictions.reflexive)) { - stun = iceRestrictions.reflexive; - } + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) { + canEnumerate = true; + } else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) { + canEnumerate = true; + } - if (!isNull(iceRestrictions.relay)) { - turn = iceRestrictions.relay; - } + var hasMicrophone = false; + var hasSpeakers = false; + var hasWebcam = false; - if (!iceRestrictions.host && !!candidate.match(/typ host/g)) { - return; - } + var isWebsiteHasMicrophonePermissions = false; + var isWebsiteHasWebcamPermissions = false; - if (!turn && !!candidate.match(/typ relay/g)) { + // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices + // todo: switch to enumerateDevices when landed in canary. + function checkDeviceSupport(callback) { + if (!canEnumerate) { return; } - if (!stun && !!candidate.match(/typ srflx/g)) { - return; - } + // This method is useful only for Chrome! - var protocol = connection.iceProtocols; + if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) { + navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack); + } - if (!protocol.udp && !!candidate.match(/ udp /g)) { - return; + if (!navigator.enumerateDevices && navigator.enumerateDevices) { + navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); } - if (!protocol.tcp && !!candidate.match(/ tcp /g)) { + if (!navigator.enumerateDevices) { + if (callback) { + callback(); + } return; } - if (connection.enableLogs) { - console.debug('Your candidate pairs:', candidate); - } + MediaDevices = []; - return { - candidate: candidate, - sdpMid: icePair.sdpMid, - sdpMLineIndex: icePair.sdpMLineIndex - }; - } + audioInputDevices = []; + audioOutputDevices = []; + videoInputDevices = []; - return { - processCandidates: processCandidates - }; - })(); + navigator.enumerateDevices(function(devices) { + devices.forEach(function(_device) { + var device = {}; + for (var d in _device) { + device[d] = _device[d]; + } - // IceServersHandler.js - // note: "urls" doesn't works in old-firefox. + // if it is MediaStreamTrack.getSources + if (device.kind === 'audio') { + device.kind = 'audioinput'; + } - var iceFrame, loadedIceFrame; + if (device.kind === 'video') { + device.kind = 'videoinput'; + } - function loadIceFrame(callback, skip) { - if (loadedIceFrame) return; - if (!skip) return loadIceFrame(callback, true); + var skip; + MediaDevices.forEach(function(d) { + if (d.id === device.id && d.kind === device.kind) { + skip = true; + } + }); - loadedIceFrame = true; + if (skip) { + return; + } - var iframe = document.createElement('iframe'); - iframe.onload = function() { - iframe.isLoaded = true; + if (!device.deviceId) { + device.deviceId = device.id; + } - listenEventHandler('message', iFrameLoaderCallback); + if (!device.id) { + device.id = device.deviceId; + } - function iFrameLoaderCallback(event) { - if (!event.data || !event.data.iceServers) return; - callback(event.data.iceServers); + if (!device.label) { + device.label = 'Please invoke getUserMedia once.'; + if (location.protocol !== 'https:') { + if (document.domain.search && document.domain.search(/localhost|127.0./g) === -1) { + device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; + } + } + } else { + if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) { + isWebsiteHasWebcamPermissions = true; + } - // this event listener is no more needed - window.removeEventListener('message', iFrameLoaderCallback); - } + if (device.kind === 'audioinput' && !isWebsiteHasMicrophonePermissions) { + isWebsiteHasMicrophonePermissions = true; + } + } - iframe.contentWindow.postMessage('get-ice-servers', '*'); - }; - iframe.src = 'https://cdn.webrtc-experiment.com/getIceServers/'; - iframe.style.display = 'none'; - (document.body || document.documentElement).appendChild(iframe); - } + if (device.kind === 'audioinput') { + hasMicrophone = true; - if (typeof window.getExternalIceServers === 'undefined' || window.getExternalIceServers == true) { - loadIceFrame(function(externalIceServers) { - if (!externalIceServers || !externalIceServers.length) return; - window.RMCExternalIceServers = externalIceServers; + if (audioInputDevices.indexOf(device) === -1) { + audioInputDevices.push(device); + } + } - if (window.iceServersLoadCallback && typeof window.iceServersLoadCallback === 'function') { - window.iceServersLoadCallback(externalIceServers); - } - }); - } + if (device.kind === 'audiooutput') { + hasSpeakers = true; - var IceServersHandler = (function() { - function getIceServers(connection) { - var iceServers = []; + if (audioOutputDevices.indexOf(device) === -1) { + audioOutputDevices.push(device); + } + } - // Firefox <= 37 doesn't understands "urls" + if (device.kind === 'videoinput') { + hasWebcam = true; - iceServers.push({ - urls: 'stun:stun.l.google.com:19302' - }); + if (videoInputDevices.indexOf(device) === -1) { + videoInputDevices.push(device); + } + } - iceServers.push({ - urls: 'stun:stun.anyfirewall.com:3478' - }); + // there is no 'videoouput' in the spec. - iceServers.push({ - urls: 'turn:turn.bistri.com:80', - credential: 'homeo', - username: 'homeo' - }); + if (MediaDevices.indexOf(device) === -1) { + MediaDevices.push(device); + } + }); - iceServers.push({ - urls: 'turn:turn.anyfirewall.com:443?transport=tcp', - credential: 'webrtc', - username: 'webrtc' - }); + if (typeof DetectRTC !== 'undefined') { + // to sync latest outputs + DetectRTC.MediaDevices = MediaDevices; + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; - if (window.RMCExternalIceServers) { - iceServers = window.RMCExternalIceServers.concat(iceServers); - connection.iceServers = iceServers; - } else if (typeof window.getExternalIceServers === 'undefined' || window.getExternalIceServers == true) { - window.iceServersLoadCallback = function() { - iceServers = window.RMCExternalIceServers.concat(iceServers); - connection.iceServers = iceServers; - }; - } else { - iceServers.push({ - urls: 'turn:turn.anyfirewall.com:443?transport=udp', - credential: 'webrtc', - username: 'webrtc' - }); - } + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; - return iceServers; - } + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; + } - return { - getIceServers: getIceServers - }; - })(); + if (callback) { + callback(); + } + }); + } - // BandwidthHandler.js + // check for microphone/camera support! + checkDeviceSupport(); - var BandwidthHandler = (function() { - function setBAS(sdp, bandwidth, isScreen) { - if (!bandwidth) { - return sdp; - } + var DetectRTC = window.DetectRTC || {}; - if (typeof isFirefox !== 'undefined' && isFirefox) { - return sdp; - } + // ---------- + // DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion + DetectRTC.browser = getBrowserInfo(); - if (isScreen) { - if (!bandwidth.screen) { - console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); - } else if (bandwidth.screen < 300) { - console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); - } - } + detectPrivateMode(function(isPrivateBrowsing) { + DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing; + }); - // if screen; must use at least 300kbs - if (bandwidth.screen && isScreen) { - sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); - sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); - } + // DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge + DetectRTC.browser['is' + DetectRTC.browser.name] = true; - // remove existing bandwidth lines - if (bandwidth.audio || bandwidth.video || bandwidth.data) { - sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); - } + var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); - if (bandwidth.audio) { - sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + // --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1. + var isWebRTCSupported = false; + ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) { + if (isWebRTCSupported) { + return; } - if (bandwidth.video) { - sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n'); + if (item in window) { + isWebRTCSupported = true; } + }); + DetectRTC.isWebRTCSupported = isWebRTCSupported; - return sdp; - } - - // Find the line in sdpLines that starts with |prefix|, and, if specified, - // contains |substr| (case-insensitive search). - function findLine(sdpLines, prefix, substr) { - return findLineInRange(sdpLines, 0, -1, prefix, substr); - } + //------- + DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined'; - // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| - // and, if specified, contains |substr| (case-insensitive search). - function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { - var realEndLine = endLine !== -1 ? endLine : sdpLines.length; - for (var i = startLine; i < realEndLine; ++i) { - if (sdpLines[i].indexOf(prefix) === 0) { - if (!substr || - sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { - return i; - } - } - } - return null; + // --------- Detect if system supports screen capturing API + var isScreenCapturingSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) { + isScreenCapturingSupported = true; + } else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) { + isScreenCapturingSupported = true; } - // Gets the codec payload type from an a=rtpmap:X line. - function getCodecPayloadType(sdpLine) { - var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); - var result = sdpLine.match(pattern); - return (result && result.length === 2) ? result[1] : null; + if (location.protocol !== 'https:') { + isScreenCapturingSupported = false; } + DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported; - function setVideoBitrates(sdp, params) { - params = params || {}; - var xgoogle_min_bitrate = params.min; - var xgoogle_max_bitrate = params.max; - - var sdpLines = sdp.split('\r\n'); - - // VP8 - var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); - var vp8Payload; - if (vp8Index) { - vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); - } + // --------- Detect if WebAudio API are supported + var webAudio = { + isSupported: false, + isCreateMediaStreamSourceSupported: false + }; - if (!vp8Payload) { - return sdp; + ['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) { + if (webAudio.isSupported) { + return; } - var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); - var rtxPayload; - if (rtxIndex) { - rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); - } + if (item in window) { + webAudio.isSupported = true; - if (!rtxIndex) { - return sdp; + if ('createMediaStreamSource' in window[item].prototype) { + webAudio.isCreateMediaStreamSourceSupported = true; + } } + }); + DetectRTC.isAudioContextSupported = webAudio.isSupported; + DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported; - var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); - if (rtxFmtpLineIndex !== null) { - var appendrtxNext = '\r\n'; - appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); - sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); - sdp = sdpLines.join('\r\n'); - } + // ---------- Detect if SCTP/RTP channels are supported. - return sdp; + var isRtpDataChannelsSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) { + isRtpDataChannelsSupported = true; } + DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported; - function setOpusAttributes(sdp, params) { - params = params || {}; - - var sdpLines = sdp.split('\r\n'); - - // Opus - var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); - var opusPayload; - if (opusIndex) { - opusPayload = getCodecPayloadType(sdpLines[opusIndex]); - } + var isSCTPSupportd = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) { + isSCTPSupportd = true; + } + DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd; - if (!opusPayload) { - return sdp; - } + // --------- - var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); - if (opusFmtpLineIndex === null) { - return sdp; - } + DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js" - var appendOpusNext = ''; - appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); - appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); + // ------ + var isGetUserMediaSupported = false; + if (navigator.getUserMedia) { + isGetUserMediaSupported = true; + } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + isGetUserMediaSupported = true; + } + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && location.protocol !== 'https:') { + DetectRTC.isGetUserMediaSupported = 'Requires HTTPs'; + } + DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported; - if (typeof params.maxaveragebitrate != 'undefined') { - appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); - } + // ----------- + DetectRTC.osName = osName; + DetectRTC.osVersion = osVersion; - if (typeof params.maxplaybackrate != 'undefined') { - appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); - } + var displayResolution = ''; + if (screen.width) { + var width = (screen.width) ? screen.width : ''; + var height = (screen.height) ? screen.height : ''; + displayResolution += '' + width + ' x ' + height; + } + DetectRTC.displayResolution = displayResolution; - if (typeof params.cbr != 'undefined') { - appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); - } + // ---------- + DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing; + DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing; - if (typeof params.useinbandfec != 'undefined') { - appendOpusNext += '; useinbandfec=' + params.useinbandfec; - } + // ------ + DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress; - if (typeof params.usedtx != 'undefined') { - appendOpusNext += '; usedtx=' + params.usedtx; - } + DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING; + DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported; - if (typeof params.maxptime != 'undefined') { - appendOpusNext += '\r\na=maxptime:' + params.maxptime; + DetectRTC.checkWebSocketsSupport = function(callback) { + callback = callback || function() {}; + try { + var websocket = new WebSocket('wss://echo.websocket.org:443/'); + websocket.onopen = function() { + DetectRTC.isWebSocketsBlocked = false; + callback(); + websocket.close(); + websocket = null; + }; + websocket.onerror = function() { + DetectRTC.isWebSocketsBlocked = true; + callback(); + }; + } catch (e) { + DetectRTC.isWebSocketsBlocked = true; + callback(); } + }; - sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + // ------- + DetectRTC.load = function(callback) { + callback = callback || function() {}; + checkDeviceSupport(callback); + }; - sdp = sdpLines.join('\r\n'); - return sdp; - } + DetectRTC.MediaDevices = MediaDevices; + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; - return { - setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { - return setBAS(sdp, bandwidth, isScreen); - }, - setVideoBitrates: function(sdp, params) { - return setVideoBitrates(sdp, params); - }, - setOpusAttributes: function(sdp, params) { - return setOpusAttributes(sdp, params); - } - }; - })(); + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; - // getUserMediaHandler.js + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; - function setStreamType(constraints, stream) { - if (constraints.mandatory && constraints.mandatory.chromeMediaSource) { - stream.isScreen = true; - } else if (constraints.mozMediaSource || constraints.mediaSource) { - stream.isScreen = true; - } else if (constraints.video) { - stream.isVideo = true; - } else if (constraints.audio) { - stream.isAudio = true; + // ------ + var isSetSinkIdSupported = false; + if ('setSinkId' in document.createElement('video')) { + isSetSinkIdSupported = true; } - } - var currentUserMediaRequest = { - streams: [], - mutex: false, - queueRequests: [] - }; + DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported; - function getUserMediaHandler(options) { - if (currentUserMediaRequest.mutex === true) { - currentUserMediaRequest.queueRequests.push(options); - return; + // ----- + var isRTPSenderReplaceTracksSupported = false; + if (DetectRTC.browser.isFirefox /*&& DetectRTC.browser.version > 39*/ ) { + /*global mozRTCPeerConnection:true */ + if ('getSenders' in mozRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } + } else if (DetectRTC.browser.isChrome && typeof webkitRTCPeerConnection !== 'undefined') { + /*global webkitRTCPeerConnection:true */ + if ('getSenders' in webkitRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } } - currentUserMediaRequest.mutex = true; - - // easy way to match - var idInstance = JSON.stringify(options.localMediaConstraints); - - function streaming(stream, returnBack) { - setStreamType(options.localMediaConstraints, stream); - options.onGettingLocalMedia(stream, returnBack); + DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported; - stream.addEventListener('ended', function() { - delete currentUserMediaRequest.streams[idInstance]; + //------ + var isRemoteStreamProcessingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) { + isRemoteStreamProcessingSupported = true; + } + DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported; - currentUserMediaRequest.mutex = false; - if (currentUserMediaRequest.queueRequests.indexOf(options)) { - delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)]; - currentUserMediaRequest.queueRequests = removeNullEntries(currentUserMediaRequest.queueRequests); - } - }, false); + //------- + var isApplyConstraintsSupported = false; - currentUserMediaRequest.streams[idInstance] = { - stream: stream - }; - currentUserMediaRequest.mutex = false; + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) { + isApplyConstraintsSupported = true; + } + DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported; - if (currentUserMediaRequest.queueRequests.length) { - getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()); - } + //------- + var isMultiMonitorScreenCapturingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) { + // version 43 merely supports platforms for multi-monitors + // version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing. + isMultiMonitorScreenCapturingSupported = true; } + DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported; - if (currentUserMediaRequest.streams[idInstance]) { - streaming(currentUserMediaRequest.streams[idInstance].stream, true); - } else { - if (isPluginRTC) { - var mediaElement = document.createElement('video'); - Plugin.getUserMedia({ - audio: true, - video: true - }, function(stream) { - stream.streamid = stream.id || getRandomString(); - streaming(stream); - }, function(error) {}); + window.DetectRTC = DetectRTC; - return; - } + })(); - navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + // ios-hacks.js - if (typeof DetectRTC !== 'undefined') { - if (!DetectRTC.hasMicrophone) { - options.localMediaConstraints.audio = false; - } + function setCordovaAPIs() { + if (DetectRTC.osName !== 'iOS') return; + if (typeof cordova === 'undefined' || typeof cordova.plugins === 'undefined' || typeof cordova.plugins.iosrtc === 'undefined') return; - if (!DetectRTC.hasWebcam) { - options.localMediaConstraints.video = false; - } - } + var iosrtc = cordova.plugins.iosrtc; + window.webkitRTCPeerConnection = iosrtc.RTCPeerConnection; + window.RTCSessionDescription = iosrtc.RTCSessionDescription; + window.RTCIceCandidate = iosrtc.RTCIceCandidate; + window.MediaStream = iosrtc.MediaStream; + window.MediaStreamTrack = iosrtc.MediaStreamTrack; + navigator.getUserMedia = navigator.webkitGetUserMedia = iosrtc.getUserMedia; - navigator.getMedia(options.localMediaConstraints, function(stream) { - stream.streamid = stream.id || getRandomString(); - streaming(stream); - }, function(error) { - options.onLocalMediaError(error, options.localMediaConstraints); - }); - } + iosrtc.debug.enable('iosrtc*'); + iosrtc.registerGlobals(); } - // StreamsHandler.js - - var StreamsHandler = (function() { - function handleType(type) { - if (!type) { - return; - } + document.addEventListener('deviceready', setCordovaAPIs, false); + setCordovaAPIs(); - if (typeof type === 'string' || typeof type === 'undefined') { - return type; - } + // RTCPeerConnection.js - if (type.audio && type.video) { - return null; - } + var defaults = {}; - if (type.audio) { - return 'audio'; - } + function setSdpConstraints(config) { + var sdpConstraints; - if (type.video) { - return 'video'; - } + var sdpConstraints_mandatory = { + OfferToReceiveAudio: !!config.OfferToReceiveAudio, + OfferToReceiveVideo: !!config.OfferToReceiveVideo + }; - return; - } + sdpConstraints = { + mandatory: sdpConstraints_mandatory, + optional: [{ + VoiceActivityDetection: false + }] + }; - function setHandlers(stream, syncAction, connection) { - if (typeof syncAction == 'undefined' || syncAction == true) { - stream.addEventListener('ended', function() { - StreamsHandler.onSyncNeeded(stream.streamid, 'ended'); - }, false); - } + if (!!navigator.mozGetUserMedia && firefoxVersion > 34) { + sdpConstraints = { + OfferToReceiveAudio: !!config.OfferToReceiveAudio, + OfferToReceiveVideo: !!config.OfferToReceiveVideo + }; + } - stream.mute = function(type, isSyncAction) { - type = handleType(type); + return sdpConstraints; + } - if (typeof isSyncAction !== 'undefined') { - syncAction = isSyncAction; - } + var RTCPeerConnection; + if (typeof mozRTCPeerConnection !== 'undefined') { + RTCPeerConnection = mozRTCPeerConnection; + } else if (typeof webkitRTCPeerConnection !== 'undefined') { + RTCPeerConnection = webkitRTCPeerConnection; + } else if (typeof window.RTCPeerConnection !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } - if (typeof type == 'undefined' || type == 'audio') { - stream.getAudioTracks().forEach(function(track) { - track.enabled = false; - connection.streamEvents[stream.streamid].isAudioMuted = true; - }); - } + var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; + var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; + var MediaStreamTrack = window.MediaStreamTrack; - if (typeof type == 'undefined' || type == 'video') { - stream.getVideoTracks().forEach(function(track) { - track.enabled = false; - }); - } + window.onPluginRTCInitialized = function() { + MediaStreamTrack = window.PluginRTC.MediaStreamTrack; + RTCPeerConnection = window.PluginRTC.RTCPeerConnection; + RTCIceCandidate = window.PluginRTC.RTCIceCandidate; + RTCSessionDescription = window.PluginRTC.RTCSessionDescription; + } - if (typeof syncAction == 'undefined' || syncAction == true) { - StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type); - } + if (typeof window.PluginRTC !== 'undefined') { + window.onPluginRTCInitialized(); + } - connection.streamEvents[stream.streamid].muteType = type; + function PeerInitiator(config) { + if (!RTCPeerConnection) { + throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'; + } - fireEvent(stream, 'mute', type); - }; + var connection = config.rtcMultiConnection; - stream.unmute = function(type, isSyncAction) { - type = handleType(type); + this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra; + this.userid = config.userid; + this.streams = []; + this.channels = []; + this.connectionDescription = config.connectionDescription; - if (typeof isSyncAction !== 'undefined') { - syncAction = isSyncAction; - } + var that = this; - graduallyIncreaseVolume(); + if (config.remoteSdp) { + this.connectionDescription = config.remoteSdp.connectionDescription; + } - if (typeof type == 'undefined' || type == 'audio') { - stream.getAudioTracks().forEach(function(track) { - track.enabled = true; - connection.streamEvents[stream.streamid].isAudioMuted = false; - }); - } + var allRemoteStreams = {}; - if (typeof type == 'undefined' || type == 'video') { - stream.getVideoTracks().forEach(function(track) { - track.enabled = true; - }); + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }); - // make sure that video unmute doesn't affects audio - if (typeof type !== 'undefined' && type == 'video' && connection.streamEvents[stream.streamid].isAudioMuted) { - (function looper(times) { - if (!times) { - times = 0; - } + var peer; - times++; + var renegotiatingPeer = !!config.renegotiatingPeer; + if (config.remoteSdp) { + renegotiatingPeer = !!config.remoteSdp.renegotiatingPeer; + } - // check until five-seconds - if (times < 100 && connection.streamEvents[stream.streamid].isAudioMuted) { - stream.mute('audio'); + var localStreams = []; + connection.attachStreams.forEach(function(stream) { + if (!!stream) localStreams.push(stream); + }); - setTimeout(function() { - looper(times); - }, 50); - } - })(); + if (!renegotiatingPeer) { + peer = new RTCPeerConnection(navigator.onLine ? { + iceServers: connection.iceServers, + iceTransports: 'all' + } : null, window.PluginRTC ? null : connection.optionalArgument); + } else { + peer = config.peerRef; + + peer.getLocalStreams().forEach(function(stream) { + localStreams.forEach(function(localStream, index) { + if (stream == localStream) { + delete localStreams[index]; } - } + }); - if (typeof syncAction == 'undefined' || syncAction == true) { - StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type); - } + connection.removeStreams.forEach(function(streamToRemove, index) { + if (stream === streamToRemove) { + stream = connection.beforeRemovingStream(stream); + if (stream && !!peer.removeStream) { + peer.removeStream(stream); + } - connection.streamEvents[stream.streamid].unmuteType = type; + localStreams.forEach(function(localStream, index) { + if (streamToRemove == localStream) { + delete localStreams[index]; + } + }); + } + }); + }); + } - fireEvent(stream, 'unmute', type); + if (connection.DetectRTC.browser.name === 'Firefox') { + peer.removeStream = function(stream) { + stream.mute(); + connection.StreamsHandler.onSyncNeeded(stream.streamid, 'stream-removed'); }; + } - function graduallyIncreaseVolume() { - if (!connection.streamEvents[stream.streamid].mediaElement) { - return; + peer.onicecandidate = function(event) { + if (!event.candidate) { + if (!connection.trickleIce) { + var localSdp = peer.localDescription; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: that.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare, + isFirefoxOffered: isFirefox + }); } - - var mediaElement = connection.streamEvents[stream.streamid].mediaElement; - mediaElement.volume = 0; - afterEach(200, 5, function() { - mediaElement.volume += .20; - }); + return; } - } - function afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes) { - startedTimes = (startedTimes || 0) + 1; - if (startedTimes >= numberOfTimes) return; + if (!connection.trickleIce) return; + config.onLocalCandidate({ + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex + }); + }; - setTimeout(function() { - callback(); - afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes); - }, setTimeoutInteval); + var isFirefoxOffered = !isFirefox; + if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered) { + isFirefoxOffered = true; } - return { - setHandlers: setHandlers, - onSyncNeeded: function(streamid, action, type) {} - }; - })(); + localStreams.forEach(function(localStream) { + if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream) { + return; + } - // Last time updated at Monday, January 4th, 2016, 1:17:50 PM + if (config.dontAttachLocalStream) { + return; + } - // Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js + localStream = connection.beforeAddingStream(localStream); + if (localStream) { + peer.addStream(localStream); + } + }); - // Muaz Khan - www.MuazKhan.com - // MIT License - www.WebRTC-Experiment.com/licence - // Documentation - github.com/muaz-khan/DetectRTC - // ____________ - // DetectRTC.js + peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() { + var extra = that.extra; + if (connection.peers[that.userid]) { + extra = connection.peers[that.userid].extra || extra; + } - // DetectRTC.hasWebcam (has webcam device!) - // DetectRTC.hasMicrophone (has microphone device!) - // DetectRTC.hasSpeakers (has speakers!) + if (!peer) { + return; + } - (function() { + config.onPeerStateChanged({ + iceConnectionState: peer.iceConnectionState, + iceGatheringState: peer.iceGatheringState, + signalingState: peer.signalingState, + extra: extra, + userid: that.userid + }); + }; - 'use strict'; + var sdpConstraints = { + OfferToReceiveAudio: !!localStreams.length, + OfferToReceiveVideo: !!localStreams.length + }; - var navigator = window.navigator; + if (config.localPeerSdpConstraints) sdpConstraints = config.localPeerSdpConstraints; - if (typeof navigator !== 'undefined') { - if (typeof navigator.webkitGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.webkitGetUserMedia; + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + + peer.onaddstream = function(event) { + var streamsToShare = {}; + if (config.remoteSdp && config.remoteSdp.streamsToShare) { + streamsToShare = config.remoteSdp.streamsToShare; + } else if (config.streamsToShare) { + streamsToShare = config.streamsToShare; } - if (typeof navigator.mozGetUserMedia !== 'undefined') { - navigator.getUserMedia = navigator.mozGetUserMedia; + var streamToShare = streamsToShare[event.stream.id]; + if (streamToShare) { + event.stream.isAudio = streamToShare.isAudio; + event.stream.isVideo = streamToShare.isVideo; + event.stream.isScreen = streamToShare.isScreen; } - } else { - navigator = { - getUserMedia: function() {}, - userAgent: 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45' - }; - } + event.stream.streamid = event.stream.id; + if (!event.stream.stop) { + event.stream.stop = function() { + if (isFirefox) { + fireEvent(this, 'ended'); + } + }; + } + allRemoteStreams[event.stream.id] = event.stream; + config.onRemoteStream(event.stream); + }; - var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); - var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob); + peer.onremovestream = function(event) { + event.stream.streamid = event.stream.id; - var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; - var isFirefox = typeof window.InstallTrigger !== 'undefined'; - var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - var isChrome = !!window.chrome && !isOpera; - var isIE = !!document.documentMode && !isEdge; + if (allRemoteStreams[event.stream.id]) { + delete allRemoteStreams[event.stream.id]; + } - // this one can also be used: - // https://www.websocket.org/js/stuff.js (DetectBrowser.js) + config.onRemoteStreamRemoved(event.stream); + }; - function getBrowserInfo() { - var nVer = navigator.appVersion; - var nAgt = navigator.userAgent; - var browserName = navigator.appName; - var fullVersion = '' + parseFloat(navigator.appVersion); - var majorVersion = parseInt(navigator.appVersion, 10); - var nameOffset, verOffset, ix; + this.addRemoteCandidate = function(remoteCandidate) { + peer.addIceCandidate(new RTCIceCandidate(remoteCandidate)); + }; - // In Opera, the true version is after 'Opera' or after 'Version' - if (isOpera) { - browserName = 'Opera'; - try { - fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0]; - majorVersion = fullVersion.split('.')[0]; - } catch (e) { - fullVersion = '0.0.0.0'; - majorVersion = 0; + this.addRemoteSdp = function(remoteSdp) { + remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); + peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), function() {}, function(error) { + if (!!connection.enableLogs) { + console.error(JSON.stringify(error, null, '\t'), '\n', remoteSdp.type, remoteSdp.sdp); } - } - // In MSIE, the true version is after 'MSIE' in userAgent - else if (isIE) { - verOffset = nAgt.indexOf('MSIE'); - browserName = 'IE'; - fullVersion = nAgt.substring(verOffset + 5); - } - // In Chrome, the true version is after 'Chrome' - else if (isChrome) { - verOffset = nAgt.indexOf('Chrome'); - browserName = 'Chrome'; - fullVersion = nAgt.substring(verOffset + 7); - } - // In Safari, the true version is after 'Safari' or after 'Version' - else if (isSafari) { - verOffset = nAgt.indexOf('Safari'); - browserName = 'Safari'; - fullVersion = nAgt.substring(verOffset + 7); + }); + }; - if ((verOffset = nAgt.indexOf('Version')) !== -1) { - fullVersion = nAgt.substring(verOffset + 8); - } + var isOfferer = true; + + if (config.remoteSdp) { + isOfferer = false; + } + + if (connection.session.data === true) { + createDataChannel(); + } + + if (config.remoteSdp) { + if (config.remoteSdp.remotePeerSdpConstraints) { + sdpConstraints = config.remoteSdp.remotePeerSdpConstraints; } - // In Firefox, the true version is after 'Firefox' - else if (isFirefox) { - verOffset = nAgt.indexOf('Firefox'); - browserName = 'Firefox'; - fullVersion = nAgt.substring(verOffset + 8); + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + this.addRemoteSdp(config.remoteSdp); + } + + function createDataChannel() { + if (!isOfferer) { + peer.ondatachannel = function(event) { + var channel = event.channel; + setChannelEvents(channel); + }; + return; } - // In most other browsers, 'name/version' is at the end of userAgent - else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) { - browserName = nAgt.substring(nameOffset, verOffset); - fullVersion = nAgt.substring(verOffset + 1); + var channel = peer.createDataChannel('RTCDataChannel', {}); + setChannelEvents(channel); + } - if (browserName.toLowerCase() === browserName.toUpperCase()) { - browserName = navigator.appName; + function setChannelEvents(channel) { + // force ArrayBuffer in Firefox; which uses "Blob" by default. + channel.binaryType = 'arraybuffer'; + + channel.onmessage = function(event) { + config.onDataChannelMessage(event.data); + }; + + channel.onopen = function() { + config.onDataChannelOpened(channel); + }; + + channel.onerror = function(error) { + config.onDataChannelError(error); + }; + + channel.onclose = function(event) { + config.onDataChannelClosed(event); + }; + + channel.internalSend = channel.send; + channel.send = function(data) { + if (channel.readyState !== 'open') { + return; } - } - if (isEdge) { - browserName = 'Edge'; - // fullVersion = navigator.userAgent.split('Edge/')[1]; - fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10).toString(); + channel.internalSend(data); + }; + + peer.channel = channel; + } + + if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') { + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), + OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) + }); + } + + var streamsToShare = {}; + peer.getLocalStreams().forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + + peer[isOfferer ? 'createOffer' : 'createAnswer'](function(localSdp) { + localSdp.sdp = connection.processSdp(localSdp.sdp); + peer.setLocalDescription(localSdp); + + if (!connection.trickleIce) return; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: that.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare, + isFirefoxOffered: isFirefox + }); + }, function(error) { + if (!!connection.enableLogs) { + console.error('sdp-error', error); } + }, defaults.sdpConstraints); - // trim the fullVersion string at semicolon/space if present - if ((ix = fullVersion.indexOf(';')) !== -1) { - fullVersion = fullVersion.substring(0, ix); + peer.nativeClose = peer.close; + peer.close = function() { + if (!peer) { + return; } - if ((ix = fullVersion.indexOf(' ')) !== -1) { - fullVersion = fullVersion.substring(0, ix); + try { + if (peer.iceConnectionState.search(/closed|failed/gi) === -1) { + peer.getRemoteStreams().forEach(function(stream) { + stream.stop(); + }); + } + peer.nativeClose(); + } catch (e) {} + + peer = null; + that.peer = null; + }; + + this.peer = peer; + } + + // CodecsHandler.js + + var CodecsHandler = (function() { + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + } + + // "removeVPX" and "removeNonG722" methods are taken from github/mozilla/webrtc-landing + function removeVPX(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; } - majorVersion = parseInt('' + fullVersion, 10); + // this method is NOT reliable - if (isNaN(majorVersion)) { - fullVersion = '' + parseFloat(navigator.appVersion); - majorVersion = parseInt(navigator.appVersion, 10); + sdp = sdp.replace('a=rtpmap:100 VP8/90000\r\n', ''); + sdp = sdp.replace('a=rtpmap:101 VP9/90000\r\n', ''); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g, 'm=video $1 RTP\/SAVPF $2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g, 'm=video $1 RTP\/SAVPF $2'); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + + sdp = sdp.replace('a=rtcp-fb:120 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 ccm fir\r\n', ''); + + sdp = sdp.replace('a=rtcp-fb:101 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 ccm fir\r\n', ''); + + return sdp; + } + + function disableNACK(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; } - return { - fullVersion: fullVersion, - version: majorVersion, - name: browserName, - isPrivateBrowsing: false - }; + sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n'); + sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n'); + + return sdp; } - // via: https://gist.github.com/cou929/7973956 + function prioritize(codecMimeType, peer) { + if (!peer || !peer.getSenders || !peer.getSenders().length) { + return; + } - function retry(isDone, next) { - var currentTrial = 0, - maxRetry = 50, - interval = 10, - isTimeout = false; - var id = window.setInterval( - function() { - if (isDone()) { - window.clearInterval(id); - next(isTimeout); - } - if (currentTrial++ > maxRetry) { - window.clearInterval(id); - isTimeout = true; - next(isTimeout); + if (!codecMimeType || typeof codecMimeType !== 'string') { + throw 'Invalid arguments.'; + } + + peer.getSenders().forEach(function(sender) { + var params = sender.getParameters(); + for (var i = 0; i < params.codecs.length; i++) { + if (params.codecs[i].mimeType == codecMimeType) { + params.codecs.unshift(params.codecs.splice(i, 1)); + break; } - }, - 10 - ); + } + sender.setParameters(params); + }); } - function isIE10OrLater(userAgent) { - var ua = userAgent.toLowerCase(); - if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) { - return false; + function removeNonG722(sdp) { + return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9'); + } + + function setBAS(sdp, bandwidth, isScreen) { + if (!bandwidth) { + return sdp; } - var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua); - if (match && parseInt(match[1], 10) >= 10) { - return true; + + if (typeof isFirefox !== 'undefined' && isFirefox) { + return sdp; } - return false; - } - function detectPrivateMode(callback) { - var isPrivate; + if (isMobileDevice) { + return sdp; + } - if (window.webkitRequestFileSystem) { - window.webkitRequestFileSystem( - window.TEMPORARY, 1, - function() { - isPrivate = false; - }, - function(e) { - console.log(e); - isPrivate = true; - } - ); - } else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) { - var db; - try { - db = window.indexedDB.open('test'); - } catch (e) { - isPrivate = true; + if (isScreen) { + if (!bandwidth.screen) { + console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); + } else if (bandwidth.screen < 300) { + console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); } + } - if (typeof isPrivate === 'undefined') { - retry( - function isDone() { - return db.readyState === 'done' ? true : false; - }, - function next(isTimeout) { - if (!isTimeout) { - isPrivate = db.result ? false : true; - } - } - ); - } - } else if (isIE10OrLater(window.navigator.userAgent)) { - isPrivate = false; - try { - if (!window.indexedDB) { - isPrivate = true; + // if screen; must use at least 300kbs + if (bandwidth.screen && isScreen) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } + + // remove existing bandwidth lines + if (bandwidth.audio || bandwidth.video || bandwidth.data) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + } + + if (bandwidth.audio) { + sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + } + + if (bandwidth.video) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n'); + } + + return sdp; + } + + // Find the line in sdpLines that starts with |prefix|, and, if specified, + // contains |substr| (case-insensitive search). + function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); + } + + // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| + // and, if specified, contains |substr| (case-insensitive search). + function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; } - } catch (e) { - isPrivate = true; - } - } else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) { - try { - window.localStorage.setItem('test', 1); - } catch (e) { - isPrivate = true; } + } + return null; + } - if (typeof isPrivate === 'undefined') { - isPrivate = false; - window.localStorage.removeItem('test'); - } + // Gets the codec payload type from an a=rtpmap:X line. + function getCodecPayloadType(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + function setVideoBitrates(sdp, params) { + if (isMobileDevice) { + return sdp; } - retry( - function isDone() { - return typeof isPrivate !== 'undefined' ? true : false; - }, - function next(isTimeout) { - callback(isPrivate); - } - ); + params = params || {}; + var xgoogle_min_bitrate = params.min; + var xgoogle_max_bitrate = params.max; + + var sdpLines = sdp.split('\r\n'); + + // VP8 + var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); + var vp8Payload; + if (vp8Index) { + vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); + } + + if (!vp8Payload) { + return sdp; + } + + var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); + var rtxPayload; + if (rtxIndex) { + rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); + } + + if (!rtxIndex) { + return sdp; + } + + var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); + if (rtxFmtpLineIndex !== null) { + var appendrtxNext = '\r\n'; + appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); + sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); + sdp = sdpLines.join('\r\n'); + } + + return sdp; } - 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()); - }, - getOsName: function() { - var osName = 'Unknown OS'; - if (isMobile.Android()) { - osName = 'Android'; - } + function setOpusAttributes(sdp, params) { + if (isMobileDevice) { + return sdp; + } - if (isMobile.BlackBerry()) { - osName = 'BlackBerry'; - } + params = params || {}; - if (isMobile.iOS()) { - osName = 'iOS'; - } + var sdpLines = sdp.split('\r\n'); - if (isMobile.Opera()) { - osName = 'Opera Mini'; - } + // Opus + var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); + var opusPayload; + if (opusIndex) { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } - if (isMobile.Windows()) { - osName = 'Windows'; - } + if (!opusPayload) { + return sdp; + } - return osName; + var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + if (opusFmtpLineIndex === null) { + return sdp; } - }; - // via: http://jsfiddle.net/ChristianL/AVyND/ - function detectDesktopOS() { - var unknown = '-'; + var appendOpusNext = ''; + appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); + appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); - var nVer = navigator.appVersion; - var nAgt = navigator.userAgent; + if (typeof params.maxaveragebitrate != 'undefined') { + appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); + } - var os = unknown; - var clientStrings = [{ - s: 'Windows 10', - r: /(Windows 10.0|Windows NT 10.0)/ - }, { - s: 'Windows 8.1', - r: /(Windows 8.1|Windows NT 6.3)/ - }, { - s: 'Windows 8', - r: /(Windows 8|Windows NT 6.2)/ - }, { - s: 'Windows 7', - r: /(Windows 7|Windows NT 6.1)/ - }, { - s: 'Windows Vista', - r: /Windows NT 6.0/ - }, { - s: 'Windows Server 2003', - r: /Windows NT 5.2/ - }, { - s: 'Windows XP', - r: /(Windows NT 5.1|Windows XP)/ - }, { - s: 'Windows 2000', - r: /(Windows NT 5.0|Windows 2000)/ - }, { - s: 'Windows ME', - r: /(Win 9x 4.90|Windows ME)/ - }, { - s: 'Windows 98', - r: /(Windows 98|Win98)/ - }, { - s: 'Windows 95', - r: /(Windows 95|Win95|Windows_95)/ - }, { - s: 'Windows NT 4.0', - r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ - }, { - s: 'Windows CE', - r: /Windows CE/ - }, { - s: 'Windows 3.11', - r: /Win16/ - }, { - s: 'Android', - r: /Android/ - }, { - s: 'Open BSD', - r: /OpenBSD/ - }, { - s: 'Sun OS', - r: /SunOS/ - }, { - s: 'Linux', - r: /(Linux|X11)/ - }, { - s: 'iOS', - r: /(iPhone|iPad|iPod)/ - }, { - s: 'Mac OS X', - r: /Mac OS X/ - }, { - s: 'Mac OS', - r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ - }, { - s: 'QNX', - r: /QNX/ - }, { - s: 'UNIX', - r: /UNIX/ - }, { - s: 'BeOS', - r: /BeOS/ - }, { - s: 'OS/2', - r: /OS\/2/ - }, { - s: 'Search Bot', - r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ - }]; - for (var id in clientStrings) { - var cs = clientStrings[id]; - if (cs.r.test(nAgt)) { - os = cs.s; - break; - } + if (typeof params.maxplaybackrate != 'undefined') { + appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); + } + + if (typeof params.cbr != 'undefined') { + appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); + } + + if (typeof params.useinbandfec != 'undefined') { + appendOpusNext += '; useinbandfec=' + params.useinbandfec; + } + + if (typeof params.usedtx != 'undefined') { + appendOpusNext += '; usedtx=' + params.usedtx; + } + + if (typeof params.maxptime != 'undefined') { + appendOpusNext += '\r\na=maxptime:' + params.maxptime; + } + + sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + function preferVP9(sdp) { + if (sdp.indexOf('SAVPF 100 101') === -1 || sdp.indexOf('VP9/90000') === -1) { + return sdp; + } + + return sdp.replace('SAVPF 100 101', 'SAVPF 101 100'); + } + + return { + removeVPX: removeVPX, + disableNACK: disableNACK, + prioritize: prioritize, + removeNonG722: removeNonG722, + setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { + return setBAS(sdp, bandwidth, isScreen); + }, + setVideoBitrates: function(sdp, params) { + return setVideoBitrates(sdp, params); + }, + setOpusAttributes: function(sdp, params) { + return setOpusAttributes(sdp, params); + }, + preferVP9: preferVP9 + }; + })(); + + // backward compatibility + window.BandwidthHandler = CodecsHandler; + + // OnIceCandidateHandler.js + + var OnIceCandidateHandler = (function() { + function processCandidates(connection, icePair) { + var candidate = icePair.candidate; + + var iceRestrictions = connection.candidates; + var stun = iceRestrictions.stun; + var turn = iceRestrictions.turn; + + if (!isNull(iceRestrictions.reflexive)) { + stun = iceRestrictions.reflexive; } - var osVersion = unknown; + if (!isNull(iceRestrictions.relay)) { + turn = iceRestrictions.relay; + } - if (/Windows/.test(os)) { - osVersion = /Windows (.*)/.exec(os)[1]; - os = 'Windows'; + if (!iceRestrictions.host && !!candidate.match(/typ host/g)) { + return; } - switch (os) { - case 'Mac OS X': - osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]; - break; + if (!turn && !!candidate.match(/typ relay/g)) { + return; + } - case 'Android': - osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]; - break; + if (!stun && !!candidate.match(/typ srflx/g)) { + return; + } - case 'iOS': - osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer); - osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0); - break; + var protocol = connection.iceProtocols; + + if (!protocol.udp && !!candidate.match(/ udp /g)) { + return; + } + + if (!protocol.tcp && !!candidate.match(/ tcp /g)) { + return; + } + + if (connection.enableLogs) { + console.debug('Your candidate pairs:', candidate); } return { - osName: os, - osVersion: osVersion + candidate: candidate, + sdpMid: icePair.sdpMid, + sdpMLineIndex: icePair.sdpMLineIndex }; } - var osName = 'Unknown OS'; - var osVersion = 'Unknown OS Version'; + return { + processCandidates: processCandidates + }; + })(); - if (isMobile.any()) { - osName = isMobile.getOsName(); - } else { - var osInfo = detectDesktopOS(); - osName = osInfo.osName; - osVersion = osInfo.osVersion; - } + // IceServersHandler.js - var isCanvasSupportsStreamCapturing = false; - var isVideoSupportsStreamCapturing = false; - ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) { - if (!isCanvasSupportsStreamCapturing && item in document.createElement('canvas')) { - isCanvasSupportsStreamCapturing = true; - } + var iceFrame, loadedIceFrame; - if (!isVideoSupportsStreamCapturing && item in document.createElement('video')) { - isVideoSupportsStreamCapturing = true; - } - }); + function loadIceFrame(callback, skip) { + if (loadedIceFrame) return; + if (!skip) return loadIceFrame(callback, true); - // via: https://github.com/diafygi/webrtc-ips - function DetectLocalIPAddress(callback) { - if (!DetectRTC.isWebRTCSupported) { - return; + loadedIceFrame = true; + + var iframe = document.createElement('iframe'); + iframe.onload = function() { + iframe.isLoaded = true; + + listenEventHandler('message', iFrameLoaderCallback); + + function iFrameLoaderCallback(event) { + if (!event.data || !event.data.iceServers) return; + callback(event.data.iceServers); + window.removeEventListener('message', iFrameLoaderCallback); } - if (DetectRTC.isORTCSupported) { - return; + iframe.contentWindow.postMessage('get-ice-servers', '*'); + }; + iframe.src = 'https://cdn.webrtc-experiment.com/getIceServers/'; + iframe.style.display = 'none'; + (document.body || document.documentElement).appendChild(iframe); + } + + if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { + loadIceFrame(function(externalIceServers) { + if (!externalIceServers || !externalIceServers.length) return; + window.RMCExternalIceServers = externalIceServers; + + if (window.iceServersLoadCallback && typeof window.iceServersLoadCallback === 'function') { + window.iceServersLoadCallback(externalIceServers); } + }); + } - getIPs(function(ip) { - //local IPs - if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) { - callback('Local: ' + ip); - } + function getSTUNObj(stunStr) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; + } - //assume the rest are public IPs - else { - callback('Public: ' + ip); - } - }); + var obj = {}; + obj[urlsParam] = stunStr; + return obj; + } + + function getTURNObj(turnStr, username, credential) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; } - //get the IP addresses associated with an account - function getIPs(callback) { - var ipDuplicates = {}; + var obj = { + username: username, + credential: credential + }; + obj[urlsParam] = turnStr; + return obj; + } - //compatibility for firefox and chrome - var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; - var useWebKit = !!window.webkitRTCPeerConnection; + function getExtenralIceFormatted() { + var iceServers; + window.RMCExternalIceServers.forEach(function(ice) { + if (!ice.urls) { + ice.urls = ice.url; + } - // bypass naive webrtc blocking using an iframe - if (!RTCPeerConnection) { - var iframe = document.getElementById('iframe'); - if (!iframe) { - // - throw 'NOTE: you need to have an iframe in the page right above the script tag.'; - } - var win = iframe.contentWindow; - RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection; - useWebKit = !!win.webkitRTCPeerConnection; + if (ice.urls.search('stun|stuns') !== -1) { + iceServers.push(getSTUNObj(ice.urls)); } - // if still no RTCPeerConnection then it is not supported by the browser so just return - if (!RTCPeerConnection) { - return; + if (ice.urls.search('turn|turns') !== -1) { + iceServers.push(getTURNObj(ice.urls, ice.username, ice.credential)); } + }); + return iceServers; + } - //minimal requirements for data connection - var mediaConstraints = { - optional: [{ - RtpDataChannels: true - }] - }; + var IceServersHandler = (function() { + function getIceServers(connection) { + var iceServers = []; - //firefox already has a default stun server in about:config - // media.peerconnection.default_iceservers = - // [{"url": "stun:stun.services.mozilla.com"}] - var servers; + iceServers.push(getSTUNObj('stun:stun.l.google.com:19302')); + iceServers.push(getTURNObj('turn:turn.bistri.com:80', 'homeo', 'homeo')); + iceServers.push(getTURNObj('turn:turn.anyfirewall.com:443', 'webrtc', 'webrtc')); - //add same stun server for chrome - if (useWebKit) { - servers = { - iceServers: [{ - urls: 'stun:stun.services.mozilla.com' - }] + if (window.RMCExternalIceServers) { + iceServers = iceServers.concat(getExtenralIceFormatted()); + } else if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { + connection.iceServers = iceServers; + window.iceServersLoadCallback = function() { + connection.iceServers = connection.iceServers.concat(getExtenralIceFormatted()); }; - - if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isFirefox && DetectRTC.browser.version <= 38) { - servers[0] = { - url: servers[0].urls - }; - } } - //construct a new RTCPeerConnection - var pc = new RTCPeerConnection(servers, mediaConstraints); + return iceServers; + } - function handleCandidate(candidate) { - //match just the IP address - var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; - var match = ipRegex.exec(candidate); - if (!match) { - console.warn('Could not match IP address in', candidate); - return; - } - var ipAddress = match[1]; + return { + getIceServers: getIceServers + }; + })(); - //remove duplicates - if (ipDuplicates[ipAddress] === undefined) { - callback(ipAddress); - } + // Last time updated at Fri Jan 08 2016 14:06 - ipDuplicates[ipAddress] = true; + // gumadapter.js + // https://cdn.webrtc-experiment.com/gumadapter.js + + // getUserMedia hacks from git/webrtc/adapter; + // removed redundant codes + // A-to-Zee, all copyrights goes to: + // https://github.com/webrtc/adapter/blob/master/LICENSE.md + + var getUserMedia = null; + var webrtcDetectedBrowser = null; + var webrtcDetectedVersion = null; + var webrtcMinimumVersion = null; + + var webrtcUtils = window.webrtcUtils || {}; + if (!webrtcUtils.enableLogs) { + webrtcUtils.enableLogs = true; + } + if (!webrtcUtils.log) { + webrtcUtils.log = function() { + if (!webrtcUtils.enableLogs) { + return; } - //listen for candidate events - pc.onicecandidate = function(ice) { - //skip non-candidate events - if (ice.candidate) { - handleCandidate(ice.candidate.candidate); + // 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 (!webrtcUtils.extractVersion) { + webrtcUtils.extractVersion = function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }; + } + + 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 = stream ? URL.createObjectURL(stream) : null; + } } - }; + }); + } - //create a bogus data channel - pc.createDataChannel(''); + // chrome 50+ supports promises over "play" method + HTMLMediaElement.prototype.nativePlay = HTMLMediaElement.prototype.play; + HTMLMediaElement.prototype.play = function() { + var myself = this; + var promise = myself.nativePlay(); + if (promise) { + promise.then(function() { + // maybe it is Android + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }).catch(function() { + // maybe it is iOS webview + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }); + } + }; - //create an offer sdp - pc.createOffer(function(result) { + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; + } - //trigger the stun server request - pc.setLocalDescription(result, function() {}, function() {}); + if (typeof window === 'undefined' || !window.navigator) { + webrtcDetectedBrowser = 'not a browser'; + } else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcDetectedBrowser = 'firefox'; - }, function() {}); + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); - //wait for a while to let everything done - setTimeout(function() { - //read candidate info from local description - var lines = pc.localDescription.sdp.split('\n'); + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; - lines.forEach(function(line) { - if (line.indexOf('a=candidate:') === 0) { - handleCandidate(line); + // 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]; + } } }); - }, 1000); - } - - var MediaDevices = []; + 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); + }; - var audioInputDevices = []; - var audioOutputDevices = []; - var videoInputDevices = []; + navigator.getUserMedia = getUserMedia; - if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { - // Firefox 38+ seems having support of enumerateDevices - // Thanks @xdumaine/enumerateDevices - navigator.enumerateDevices = function(callback) { - navigator.mediaDevices.enumerateDevices().then(callback); + // 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); + }); + }; - // ---------- Media Devices detection - var canEnumerate = false; - - /*global MediaStreamTrack:true */ - if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) { - canEnumerate = true; - } else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) { - canEnumerate = true; + if (webrtcDetectedVersion < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; } - var hasMicrophone = false; - var hasSpeakers = false; - var hasWebcam = false; - - var isWebsiteHasMicrophonePermissions = false; - var isWebsiteHasWebcamPermissions = false; - - // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices - // todo: switch to enumerateDevices when landed in canary. - function checkDeviceSupport(callback) { - if (!canEnumerate) { - return; - } + } else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcDetectedBrowser = 'chrome'; - // This method is useful only for Chrome! + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); - if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) { - navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack); - } + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; - if (!navigator.enumerateDevices && navigator.enumerateDevices) { - navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); + // getUserMedia constraints shim. + var constraintsToChrome = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; } - - if (!navigator.enumerateDevices) { - if (callback) { - callback(); + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; } - 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; + }; - MediaDevices = []; + 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; - audioInputDevices = []; - audioOutputDevices = []; - videoInputDevices = []; + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: requestUserMedia + }; + } - navigator.enumerateDevices(function(devices) { - devices.forEach(function(_device) { - var device = {}; - for (var d in _device) { - device[d] = _device[d]; - } + // 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); + }; + } - // if it is MediaStreamTrack.getSources - if (device.kind === 'audio') { - device.kind = 'audioinput'; - } + // 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.'); + }; + } - if (device.kind === 'video') { - device.kind = 'videoinput'; - } + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; - var skip; - MediaDevices.forEach(function(d) { - if (d.id === device.id && d.kind === device.kind) { - skip = true; - } - }); + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); - if (skip) { - return; - } + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; + } else { + webrtcUtils.log('Browser does not appear to be WebRTC-capable'); + } - if (!device.deviceId) { - device.deviceId = device.id; - } + // Returns the result of getUserMedia as a Promise. + function requestUserMedia(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); + } - if (!device.id) { - device.id = device.deviceId; - } + if (typeof module !== 'undefined') { + module.exports = { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; + } else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; + }); + } - if (!device.label) { - device.label = 'Please invoke getUserMedia once.'; - if (location.protocol !== 'https:') { - device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; - } - } else { - if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) { - isWebsiteHasWebcamPermissions = true; - } + // getUserMediaHandler.js - if (device.kind === 'audioinput' && !isWebsiteHasMicrophonePermissions) { - isWebsiteHasMicrophonePermissions = true; - } - } + if (typeof webrtcUtils !== 'undefined') { + webrtcUtils.enableLogs = false; + } - if (device.kind === 'audioinput') { - hasMicrophone = true; + function setStreamType(constraints, stream) { + if (constraints.mandatory && constraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } else if (constraints.mozMediaSource || constraints.mediaSource) { + stream.isScreen = true; + } else if (constraints.video) { + stream.isVideo = true; + } else if (constraints.audio) { + stream.isAudio = true; + } + } - if (audioInputDevices.indexOf(device) === -1) { - audioInputDevices.push(device); - } - } + var currentUserMediaRequest = { + streams: [], + mutex: false, + queueRequests: [], + remove: function(idInstance) { + this.mutex = false; - if (device.kind === 'audiooutput') { - hasSpeakers = true; + var stream = this.streams[idInstance]; + if (!stream) { + return; + } - if (audioOutputDevices.indexOf(device) === -1) { - audioOutputDevices.push(device); - } - } + stream = stream.stream; - if (device.kind === 'videoinput') { - hasWebcam = true; + var options = stream.currentUserMediaRequestOptions; - if (videoInputDevices.indexOf(device) === -1) { - videoInputDevices.push(device); - } - } + if (this.queueRequests.indexOf(options)) { + delete this.queueRequests[this.queueRequests.indexOf(options)]; + this.queueRequests = removeNullEntries(this.queueRequests); + } - // there is no 'videoouput' in the spec. + this.streams[idInstance].stream = null; + delete this.streams[idInstance]; + } + }; - if (MediaDevices.indexOf(device) === -1) { - MediaDevices.push(device); - } - }); + function getUserMediaHandler(options) { + if (currentUserMediaRequest.mutex === true) { + currentUserMediaRequest.queueRequests.push(options); + return; + } + currentUserMediaRequest.mutex = true; - if (typeof DetectRTC !== 'undefined') { - // to sync latest outputs - DetectRTC.MediaDevices = MediaDevices; - DetectRTC.hasMicrophone = hasMicrophone; - DetectRTC.hasSpeakers = hasSpeakers; - DetectRTC.hasWebcam = hasWebcam; + // easy way to match + var idInstance = JSON.stringify(options.localMediaConstraints); - DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; - DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + function streaming(stream, returnBack) { + setStreamType(options.localMediaConstraints, stream); + options.onGettingLocalMedia(stream, returnBack); - DetectRTC.audioInputDevices = audioInputDevices; - DetectRTC.audioOutputDevices = audioOutputDevices; - DetectRTC.videoInputDevices = videoInputDevices; - } + stream.addEventListener('ended', function() { + delete currentUserMediaRequest.streams[idInstance]; - if (callback) { - callback(); + currentUserMediaRequest.mutex = false; + if (currentUserMediaRequest.queueRequests.indexOf(options)) { + delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)]; + currentUserMediaRequest.queueRequests = removeNullEntries(currentUserMediaRequest.queueRequests); } - }); - } + }, false); - // check for microphone/camera support! - checkDeviceSupport(); + currentUserMediaRequest.streams[idInstance] = { + stream: stream + }; + currentUserMediaRequest.mutex = false; - var DetectRTC = window.DetectRTC || {}; + if (currentUserMediaRequest.queueRequests.length) { + getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()); + } + } - // ---------- - // DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion - DetectRTC.browser = getBrowserInfo(); + if (currentUserMediaRequest.streams[idInstance]) { + streaming(currentUserMediaRequest.streams[idInstance].stream, true); + } else { + if (isPluginRTC && window.PluginRTC) { + var mediaElement = document.createElement('video'); + window.PluginRTC.getUserMedia({ + audio: true, + video: true + }, function(stream) { + stream.streamid = stream.id || getRandomString(); + streaming(stream); + }, function(error) {}); - detectPrivateMode(function(isPrivateBrowsing) { - DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing; - }); + return; + } - // DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge - DetectRTC.browser['is' + DetectRTC.browser.name] = true; + navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream) { + stream.streamid = stream.streamid || stream.id || getRandomString(); + stream.idInstance = idInstance; + streaming(stream); + }).catch(function(error) { + options.onLocalMediaError(error, options.localMediaConstraints); + }); + } + } - var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); + // StreamsHandler.js - // --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1. - var isWebRTCSupported = false; - ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) { - if (isWebRTCSupported) { + var StreamsHandler = (function() { + function handleType(type) { + if (!type) { return; } - if (item in window) { - isWebRTCSupported = true; + if (typeof type === 'string' || typeof type === 'undefined') { + return type; } - }); - DetectRTC.isWebRTCSupported = isWebRTCSupported; - //------- - DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined'; + if (type.audio && type.video) { + return null; + } - // --------- Detect if system supports screen capturing API - var isScreenCapturingSupported = false; - if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) { - isScreenCapturingSupported = true; - } else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) { - isScreenCapturingSupported = true; - } + if (type.audio) { + return 'audio'; + } - if (location.protocol !== 'https:') { - isScreenCapturingSupported = false; + if (type.video) { + return 'video'; + } + + return; } - DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported; - // --------- Detect if WebAudio API are supported - var webAudio = { - isSupported: false, - isCreateMediaStreamSourceSupported: false - }; + function setHandlers(stream, syncAction, connection) { + if (!stream || !stream.addEventListener) return; - ['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) { - if (webAudio.isSupported) { - return; + if (typeof syncAction == 'undefined' || syncAction == true) { + stream.addEventListener('ended', function() { + StreamsHandler.onSyncNeeded(this.streamid, 'ended'); + }, false); } - if (item in window) { - webAudio.isSupported = true; + stream.mute = function(type, isSyncAction) { + type = handleType(type); - if ('createMediaStreamSource' in window[item].prototype) { - webAudio.isCreateMediaStreamSourceSupported = true; + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; } - } - }); - DetectRTC.isAudioContextSupported = webAudio.isSupported; - DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported; - // ---------- Detect if SCTP/RTP channels are supported. + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = false; + connection.streamEvents[stream.streamid].isAudioMuted = true; + }); + } - var isRtpDataChannelsSupported = false; - if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) { - isRtpDataChannelsSupported = true; - } - DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported; + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = false; + }); + } - var isSCTPSupportd = false; - if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) { - isSCTPSupportd = true; - } else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) { - isSCTPSupportd = true; - } else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) { - isSCTPSupportd = true; - } - DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd; + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type); + } - // --------- + connection.streamEvents[stream.streamid].muteType = type || 'both'; - DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js" + fireEvent(stream, 'mute', type); + }; - // ------ - var isGetUserMediaSupported = false; - if (navigator.getUserMedia) { - isGetUserMediaSupported = true; - } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - isGetUserMediaSupported = true; - } - if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && location.protocol !== 'https:') { - DetectRTC.isGetUserMediaSupported = 'Requires HTTPs'; - } - DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported; + stream.unmute = function(type, isSyncAction) { + type = handleType(type); - // ----------- - DetectRTC.osName = osName; - DetectRTC.osVersion = osVersion; + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; + } - var displayResolution = ''; - if (screen.width) { - var width = (screen.width) ? screen.width : ''; - var height = (screen.height) ? screen.height : ''; - displayResolution += '' + width + ' x ' + height; - } - DetectRTC.displayResolution = displayResolution; + graduallyIncreaseVolume(); - // ---------- - DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing; - DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing; + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = true; + connection.streamEvents[stream.streamid].isAudioMuted = false; + }); + } - // ------ - DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress; + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = true; + }); - DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING; - DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported; + // make sure that video unmute doesn't affects audio + if (typeof type !== 'undefined' && type == 'video' && connection.streamEvents[stream.streamid].isAudioMuted) { + (function looper(times) { + if (!times) { + times = 0; + } - DetectRTC.checkWebSocketsSupport = function(callback) { - callback = callback || function() {}; - try { - var websocket = new WebSocket('wss://echo.websocket.org:443/'); - websocket.onopen = function() { - DetectRTC.isWebSocketsBlocked = false; - callback(); - websocket.close(); - websocket = null; - }; - websocket.onerror = function() { - DetectRTC.isWebSocketsBlocked = true; - callback(); - }; - } catch (e) { - DetectRTC.isWebSocketsBlocked = true; - callback(); - } - }; + times++; - // ------- - DetectRTC.load = function(callback) { - callback = callback || function() {}; - checkDeviceSupport(callback); - }; + // check until five-seconds + if (times < 100 && connection.streamEvents[stream.streamid].isAudioMuted) { + stream.mute('audio'); - DetectRTC.MediaDevices = MediaDevices; - DetectRTC.hasMicrophone = hasMicrophone; - DetectRTC.hasSpeakers = hasSpeakers; - DetectRTC.hasWebcam = hasWebcam; + setTimeout(function() { + looper(times); + }, 50); + } + })(); + } + } - DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; - DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type); + } - DetectRTC.audioInputDevices = audioInputDevices; - DetectRTC.audioOutputDevices = audioOutputDevices; - DetectRTC.videoInputDevices = videoInputDevices; + connection.streamEvents[stream.streamid].unmuteType = type || 'both'; - // ------ - var isSetSinkIdSupported = false; - if ('setSinkId' in document.createElement('video')) { - isSetSinkIdSupported = true; - } - DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported; + fireEvent(stream, 'unmute', type); + }; - // ----- - var isRTPSenderReplaceTracksSupported = false; - if (DetectRTC.browser.isFirefox /*&& DetectRTC.browser.version > 39*/ ) { - /*global mozRTCPeerConnection:true */ - if ('getSenders' in mozRTCPeerConnection.prototype) { - isRTPSenderReplaceTracksSupported = true; - } - } else if (DetectRTC.browser.isChrome) { - /*global webkitRTCPeerConnection:true */ - if ('getSenders' in webkitRTCPeerConnection.prototype) { - isRTPSenderReplaceTracksSupported = true; - } - } - DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported; + function graduallyIncreaseVolume() { + if (!connection.streamEvents[stream.streamid].mediaElement) { + return; + } - //------ - var isRemoteStreamProcessingSupported = false; - if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) { - isRemoteStreamProcessingSupported = true; + var mediaElement = connection.streamEvents[stream.streamid].mediaElement; + mediaElement.volume = 0; + afterEach(200, 5, function() { + mediaElement.volume += .20; + }); + } } - DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported; - //------- - var isApplyConstraintsSupported = false; - - /*global MediaStreamTrack:true */ - if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) { - isApplyConstraintsSupported = true; - } - DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported; + function afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes) { + startedTimes = (startedTimes || 0) + 1; + if (startedTimes >= numberOfTimes) return; - //------- - var isMultiMonitorScreenCapturingSupported = false; - if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) { - // version 43 merely supports platforms for multi-monitors - // version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing. - isMultiMonitorScreenCapturingSupported = true; + setTimeout(function() { + callback(); + afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes); + }, setTimeoutInteval); } - DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported; - - window.DetectRTC = DetectRTC; + return { + setHandlers: setHandlers, + onSyncNeeded: function(streamid, action, type) {} + }; })(); // Last time updated at Oct 24, 2015, 08:32:23 @@ -4262,7 +5022,9 @@ callback(null, 'firefox', { video: { mozMediaSource: 'window', - mediaSource: 'window' + mediaSource: 'window', + width: 29999, + height: 8640 } }); return; @@ -4296,8 +5058,12 @@ video: { mandatory: { chromeMediaSource: error ? 'screen' : 'desktop', - maxWidth: window.screen.width > 1920 ? window.screen.width : 1920, - maxHeight: window.screen.height > 1080 ? window.screen.height : 1080 + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true }, optional: [] } @@ -4368,7 +5134,9 @@ callback(null, 'firefox', { video: { mozMediaSource: 'window', - mediaSource: 'window' + mediaSource: 'window', + width: 29999, + height: 8640 } }); return; @@ -4402,8 +5170,12 @@ video: { mandatory: { chromeMediaSource: error ? 'screen' : 'desktop', - maxWidth: window.screen.width > 1920 ? window.screen.width : 1920, - maxHeight: window.screen.height > 1080 ? window.screen.height : 1080 + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true }, optional: [] } diff --git a/RTCMultiConnection/RTCMultiConnection.min.js b/RTCMultiConnection/RTCMultiConnection.min.js index e000435d..dc89fe7f 100644 --- a/RTCMultiConnection/RTCMultiConnection.min.js +++ b/RTCMultiConnection/RTCMultiConnection.min.js @@ -1,6 +1,6 @@ -// Last time updated at Monday, January 11th, 2016, 4:20:40 PM +// Last time updated: 2016-03-17 11:15:55 AM UTC -"use strict";!function(){function RTCMultiConnection(roomid){function onUserLeft(remoteUserId){connection.peers[remoteUserId]&&connection.peers[remoteUserId].peer&&(connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()}),connection.peers[remoteUserId].peer.close(),connection.peers[remoteUserId].peer=null,connection.onleave({userid:remoteUserId,extra:connection.peers[remoteUserId].extra})),delete connection.peers[remoteUserId]}function connectSocket(connectCallback){if(socket)return void(connectCallback&&connectCallback(socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}socket=new SocketConnection(connection,function(s){socket=s,connectCallback&&connectCallback(socket)})}function beforeUnload(shiftModerationControlOnLeave){if(connection.closeBeforeUnload&&(connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0,autoCloseEntireSession:!!connection.autoCloseEntireSession},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close()}),socket&&("undefined"!=typeof socket.disconnect&&(connection.autoReDialOnFailure=!1,socket.disconnect()),socket=null),connection.broadcasters.length&&!connection.autoCloseEntireSession)){var firstBroadcaster=connection.broadcasters[0],otherBroadcasters=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==firstBroadcaster&&otherBroadcasters.push(broadcaster)}),connection.shiftModerationControl(firstBroadcaster,otherBroadcasters,"undefined"!=typeof shiftModerationControlOnLeave?shiftModerationControlOnLeave:!0),connection.broadcasters=[],connection.isInitiator=!1}}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}function setStreamEndHandler(stream){stream.alreadySetEndHandler||(stream.alreadySetEndHandler=!0,stream.addEventListener("ended",function(){delete connection.attachStreams[connection.attachStreams.indexOf(stream)],-1===connection.removeStreams.indexOf(stream)&&connection.removeStreams.push(stream),connection.attachStreams=removeNullEntries(connection.attachStreams),connection.removeStreams=removeNullEntries(connection.removeStreams);var streamEvent=connection.streamEvents[stream];streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},!1))}var connection=this;connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection);mPeer.onGettingLocalMedia=function(stream){setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,-1===connection.attachStreams.indexOf(stream)&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream),isAudioMuted:!0},setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])})},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream)},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])})},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){connectSocket(function(){socket.emit(connection.socketMessageEvent,"password"in message?message:{remoteUserId:message.remoteUserId||remoteUserId,message:message,sender:connection.userid},callback||function(){})})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){socket&&socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer&&connection.peers[remoteUserId].peer.close(),delete connection.peers[remoteUserId])},connection.broadcasters=[],connection.socketOptions={transport:"polling"};var socket;connection.openOrJoin=function(localUserid,password){connection.checkPresence(localUserid,function(isRoomExists,roomid){if("function"==typeof password&&(password(isRoomExists,roomid),password=null),isRoomExists){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:password||!1};return void mPeer.onNegotiationNeeded(connectionDescription)}connection.userid;connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",socket.emit("changed-uuid",connection.userid),password&&socket.emit("set-password",password),connection.isInitiator=!0,isData(connection.session)||connection.captureUserMedia()})},connection.open=function(localUserid,isPublicModerator){connection.userid;return connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connection.isInitiator=!0,connectSocket(function(){socket.emit("changed-uuid",connection.userid),1==isPublicModerator&&connection.becomePublicModerator()}),isData(connection.session)?void("function"==typeof isPublicModerator&&isPublicModerator()):void connection.captureUserMedia("function"==typeof isPublicModerator?isPublicModerator:null)},connection.becomePublicModerator=function(){connection.isInitiator&&socket.emit("become-a-public-moderator")},connection.rejoin=function(connectionDescription){if(!connection.isInitiator){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.peers[connectionDescription.remoteUserId].peer&&(connection.peers[connectionDescription.remoteUserId].peer=null),delete connection.peers[connectionDescription.remoteUserId]),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=connection.connect=function(remoteUserId,options){connection.sessionid=(remoteUserId?remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId:!1)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{},"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:!1};return connectSocket(function(){connection.peers[connection.sessionid]||mPeer.onNegotiationNeeded(connectionDescription)}),connectionDescription},connection.connectWithAllParticipants=function(remoteUserId){mPeer.onNegotiationNeeded("connectWithAllParticipants",remoteUserId||connection.sessionid)},connection.removeFromBroadcastersList=function(remoteUserId){mPeer.onNegotiationNeeded("removeFromBroadcastersList",remoteUserId||connection.sessionid),connection.peers.getAllParticipants(remoteUserId||connection.sessionid).forEach(function(participant){mPeer.onNegotiationNeeded("dropPeerConnection",participant),connection.peers[participant].peer.close(),connection.peers[participant].peer=null,delete connection.peers[participant]}),connection.attachStreams.forEach(function(stream){stream.addEventListener("ended",function(){connection.renegotiate(remoteUserId||connection.sessionid)},!1),stream.stop()})},connection.getUserMedia=connection.captureUserMedia=function(callback,session){function invokeGetUserMedia(localMediaConstraints,getUserMedia_callback){var isScreen=!1;localMediaConstraints&&(isScreen=localMediaConstraints.isScreen,delete localMediaConstraints.isScreen),getUserMediaHandler({onGettingLocalMedia:function(stream){return stream.isAudio=stream.isVideo=stream.isScreen=!1,isScreen?stream.isScreen=!0:session.audio&&session.video?stream.isVideo=!0:session.audio&&(stream.isAudio=!0),mPeer.onGettingLocalMedia(stream),getUserMedia_callback?getUserMedia_callback():void(callback&&callback(stream))},onLocalMediaError:function(error){mPeer.onLocalMediaError(error),callback&&callback()},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return session=session||connection.session,connection.dontCaptureUserMedia||isData(session)?void(callback&&callback()):void((session.audio||session.video||session.screen)&&(session.screen?getScreenConstraints(function(error,screen_constraints){if(error)throw error;invokeGetUserMedia({video:screen_constraints,isScreen:!0},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",beforeUnload,!1),connection.userid=getRandomString(),connection.changeUserId=function(newUserId){connection.userid=newUserId||getRandomString(),socket.emit("changed-uuid",connection.userid)},connection.extra={},Object.observe&&Object.observe(connection.extra,function(changes){socket.emit("extra-data-updated",connection.extra)}),connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.mediaConstraints={audio:{mandatory:{},optional:[]},video:{mandatory:{},optional:[]}},DetectRTC.load(function(){DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&(connection.mediaConstraints.audio={optional:[{sourceId:device.id}],mandatory:{}},isFirefox&&(connection.mediaConstraints.audio={deviceId:device.id})),"videoinput"===device.kind&&(connection.mediaConstraints.video={optional:[{sourceId:device.id}],mandatory:{}},isFirefox&&(connection.mediaConstraints.video={deviceId:device.id}))})}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){beforeUnload(!1)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.attachStreams=[],connection.removeStreams=[],Array.prototype.getStreamById=function(streamid){var stream;return this.forEach(function(_stream){_stream.streamid==streamid&&(stream=_stream)}),stream},connection.removeStream=function(streamid){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?void(-1===connection.removeStreams.indexOf(stream)&&(connection.removeStreams.push(stream),connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)}))):void console.warn("No such stream exists.",streamid)},connection.addStream=function(session){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints?localMediaConstraints.video:connection.mediaConstraints;return videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream),session.streamCallback&&session.streamCallback(stream),callback?callback():void connection.renegotiate()},onLocalMediaError:function(error){return mPeer.onLocalMediaError(error),callback?callback():void connection.renegotiate()},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return isData(session)?void connection.renegotiate():void((!session.audio||session.video||session.screen)&&(session.screen?getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){return mPeer.onGettingLocalMedia(stream),callback?callback():void connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)},onLocalMediaError:function(error){mPeer.onLocalMediaError(error),callback&&callback()},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void this.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return void replaceTrack(session.getVideoTracks()[0],remoteUserId,isVideoTrack);if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(!session.audio||session.video||session.screen)&&(session.screen?getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia())},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},Object.observe&&Object.observe(connection.attachStreams,function(changes){changes.forEach(function(change){"add"===change.type&&setStreamEndHandler(change.object[change.name]),("remove"===change.type||"delete"===change.type)&&-1===connection.removeStreams.indexOf(change.object[change.name])&&connection.removeStreams.push(change.object[change.name]),connection.attachStreams=removeNullEntries(connection.attachStreams),connection.removeStreams=removeNullEntries(connection.removeStreams)})}),connection.onMediaError=function(error){connection.enableLogs&&console.error(error)},connection.addNewBroadcaster=function(broadcasterId,userPreferences){connection.broadcasters.forEach(function(broadcaster){mPeer.onNegotiationNeeded({newParticipant:broadcasterId,userPreferences:userPreferences||!1},broadcaster)}),connection.session.oneway||"many-to-many"!==connection.direction||-1!==connection.broadcasters.indexOf(broadcasterId)||connection.broadcasters.push(broadcasterId)},connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),connection.autoCloseEntireSession=!1,"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},connection.onShiftedModerationControl=function(sender,existingBroadcasters){connection.acceptModerationControl(sender,existingBroadcasters)},connection.acceptModerationControl=function(sender,existingBroadcasters){connection.isInitiator=!0,connection.broadcasters=existingBroadcasters,connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({changedUUID:sender,oldUUID:connection.userid,newUUID:sender},participant)}),connection.userid=sender,socket.emit("changed-uuid",connection.userid)},connection.shiftModerationControl=function(remoteUserId,existingBroadcasters,firedOnLeave){mPeer.onNegotiationNeeded({shiftedModerationControl:!0,broadcasters:existingBroadcasters,firedOnLeave:!!firedOnLeave},remoteUserId)},connection.processSdp=function(sdp){return sdp=BandwidthHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen),sdp=BandwidthHandler.setVideoBitrates(sdp,{min:connection.bandwidth.video,max:connection.bandwidth.video}),sdp=BandwidthHandler.setOpusAttributes(sdp)},"undefined"!=typeof BandwidthHandler&&(connection.BandwidthHandler=BandwidthHandler),connection.bandwidth={screen:300,audio:50,video:256},connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.selectSingleFile(callback)},connection.getPublicModerators=function(userIdStartsWith,callback){"function"==typeof userIdStartsWith&&(callback=userIdStartsWith),connectSocket(function(socket){socket.emit("get-public-moderators","string"==typeof userIdStartsWith?userIdStartsWith:"",callback)})},connection.onmute=function(e){e.mediaElement&&((e.stream.isVideo||e.stream.isScreen)&&(e.mediaElement.pause(),e.mediaElement.setAttribute("poster",e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png")),e.stream.isAudio&&(e.mediaElement.muted=!0))},connection.onunmute=function(e){e.mediaElement&&((e.stream.isVideo||e.stream.isScreen)&&(e.mediaElement.play(),e.mediaElement.removeAttribute("poster")),e.stream.isAudio&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.onJoinWithPassword=function(remoteUserId){console.warn(remoteUserId,"is password protected. Please join with password.")},connection.onInvalidPassword=function(remoteUserId,oldPassword){console.warn(remoteUserId,"is password protected. Please join with valid password. Your old password",oldPassword,"is wrong.")},connection.onPasswordMaxTriesOver=function(remoteUserId){console.warn(remoteUserId,"is password protected. Your max password tries exceeded the limit.")},connection.getAllParticipants=function(){return connection.peers.getAllParticipants()},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.getAllVideos=function(remoteUserId){var videos=[];return Array.prototype.slice.call(document.querySelectorAll("video")).forEach(function(video){video.getAttribute("data-userid")===remoteUserId&&videos.push(video)}),videos},connection.connectSocket=function(callback){connectSocket(callback)},connection.getSocket=function(callback){return socket?callback&&callback(socket):connectSocket(callback),socket},connection.getRemoteStreams=mPeer.getRemoteStreams,connection.autoReDialOnFailure=!0;var skipStreams=["selectFirst","selectAll","forEach"];connection.streamEvents={selectFirst:function(options){if(!options){var firstStream;for(var str in connection.streamEvents)-1!==skipStreams.indexOf(str)||firstStream||(firstStream=connection.streamEvents[str]);return firstStream}},selectAll:function(){}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=15e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(remoteUserId,callback){mPeer.onNegotiationNeeded({detectPresence:!0,userid:(remoteUserId||connection.sessionid)+""},"system",callback)},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.singleBroadcastAttendees=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")}}function SocketConnection(connection,connectCallback){var parameters="";parameters+="?userid="+connection.userid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&singleBroadcastAttendees="+connection.singleBroadcastAttendees);var socket=io.connect((connection.socketURL||"/")+parameters,connection.socketOptions),mPeer=connection.multiPeersHandler;return socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}))}),socket.on(connection.socketMessageEvent,function(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra})),message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action)return void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void stream.stream[action](type)}if("connectWithAllParticipants"===message.message)return-1===connection.broadcasters.indexOf(message.sender)&&connection.broadcasters.push(message.sender),void mPeer.onNegotiationNeeded({allParticipants:connection.peers.getAllParticipants(message.sender)},message.sender);if("removeFromBroadcastersList"===message.message)return void(-1!==connection.broadcasters.indexOf(message.sender)&&(delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)],connection.broadcasters=removeNullEntries(connection.broadcasters)));if("dropPeerConnection"===message.message)return void(connection.peers[message.sender]&&(connection.peers[message.sender].peer.close(),connection.peers[message.sender].peer=null,delete connection.peers[message.sender]));if(message.message.allParticipants)return-1===message.message.allParticipants.indexOf(message.sender)&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if((message.message.readyForOffer||message.message.addMeAsBroadcaster)&&connection.addNewBroadcaster(message.sender),message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&(connection.peers[message.sender].peer&&(connection.peers[message.sender].peer.close(),connection.peers[message.sender].peer=null),delete connection.peers[message.sender]);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){("undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction)&&connection.addNewBroadcaster(message.sender,userPreferences), -(connection.session.oneway||"one-way"===connection.direction||isData(connection.session))&&connection.addNewBroadcaster(message.sender,userPreferences)}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.shiftedModerationControl?void connection.onShiftedModerationControl(message.sender,message.message.broadcasters):(message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender))}}),socket.on("user-left",function(userid){onUserLeft(userid),connection.onUserStatusChanged({userid:userid,status:"offline",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}}),connection.onleave({userid:userid,extra:{}})}),socket.on("connect",function(){connection.enableLogs&&console.info("socket.io connection is opened."),socket.emit("extra-data-updated",connection.extra),connectCallback&&connectCallback(socket)}),socket.on("disconnect",function(){connection.enableLogs&&(console.info("socket.io connection is closed"),console.warn("socket.io reconnecting"))}),socket.on("join-with-password",function(remoteUserId){connection.onJoinWithPassword(remoteUserId)}),socket.on("invalid-password",function(remoteUserId,oldPassword){connection.onInvalidPassword(remoteUserId,oldPassword)}),socket.on("password-max-tries-over",function(remoteUserId){connection.onPasswordMaxTriesOver(remoteUserId)}),socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.peers[remoteUserId]&&connection.peers[remoteUserId].peer&&(connection.peers[remoteUserId].peer.close(),delete connection.peers[remoteUserId]))}),socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),socket}function MultiPeers(connection){function initFileBufferReader(){fbr=new FileBufferReader,fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},fbr.onBegin=function(file){connection.onFileStart(file)},fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)-1==skipPeers.indexOf(peer)&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)-1==skipPeers.indexOf(peer)&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)-1==skipPeers.indexOf(peer)&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return void remoteUser.channels.forEach(function(channel){channel.send(data)})}this.getAllParticipants().forEach(function(participant){that[participant].channels.forEach(function(channel){channel.send(data)})})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},session:connection.session,rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,remoteUserId:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,optionalArgument:connection.optionalArgument,iceServers:connection.iceServers,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,enableDataChannels:!!connection.session.data,localStreams:connection.attachStreams,removeStreams:connection.removeStreams,onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void fbr.getNextChunk(message.uuid,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):void fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){if(connection.peers[remoteUserId].streams.push(stream),isPluginRTC){var mediaElement=document.createElement("video"),body=document.body||document.documentElement;return body.insertBefore(mediaElement,body.firstChild),void setTimeout(function(){Plugin.attachMediaStream(mediaElement,stream),self.onGettingRemoteMedia(mediaElement,remoteUserId)},3e3)}self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),-1!==states.iceConnectionState.search(/closed|failed/gi)&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))},processSdp:connection.processSdp}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{});var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exists.";userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exists.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&rtpSender.track instanceof VideoStreamTrack&&rtpSender.replaceTrack(track),!isVideoTrack&&rtpSender.track instanceof AudioStreamTrack&&rtpSender.replaceTrack(track)}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){if(connection.attachStreams.length){var streamsToShare={};return connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,void self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}var localMediaConstraints={},userPreferences=message.userPreferences;userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video),getUserMediaHandler({onGettingLocalMedia:function(localStream){self.onGettingLocalMedia(localStream);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},onLocalMediaError:function(error){self.onLocalMediaError(error),self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},localMediaConstraints:localMediaConstraints})}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error){connection.enableLogs&&console.error("onLocalMediaError",JSON.stringify(error,null," ")),connection.onMediaError(error)};var fbr;this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){connection.peers[remoteUserId].channels.push(channel),connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel})},this.onPeerStateChanged=function(state){connection.enableLogs&&-1!==state.iceConnectionState.search(/disconnected|closed|failed/gi)&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]},this.isPluginRTC=connection.isPluginRTC=isPluginRTC}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}function setMuteHandlers(connection,streamEvent){streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1)}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&-1===navigator.userAgent.indexOf("Safari")){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;l>i;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function getRMCMediaElement(stream,callback){var isAudioOnly=!1;stream.getVideoTracks().length||(isAudioOnly=!0);var mediaElement=document.createElement(isAudioOnly?"audio":"video");return isPluginRTC?(connection.videosContainer.insertBefore(mediaElement,connection.videosContainer.firstChild),void setTimeout(function(){Plugin.attachMediaStream(mediaElement,stream),callback(mediaElement)},1e3)):(mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:window.URL.createObjectURL(stream),mediaElement.controls=!0,isFirefox&&mediaElement.addEventListener("ended",function(){stream.onended&&stream.onended(),fireEvent(stream,"ended",stream.type)},!1),mediaElement.play(),void callback(mediaElement))}function listenEventHandler(eventName,eventHandler){window.removeEventListener(eventName,eventHandler),window.addEventListener(eventName,eventHandler,!1)}function removeNullEntries(array){var newArray=[];return array.forEach(function(item){item&&newArray.push(item)}),newArray}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function onPluginRTCInitialized(pluginRTCObject){Plugin=pluginRTCObject,MediaStreamTrack=Plugin.MediaStreamTrack,RTCPeerConnection=Plugin.RTCPeerConnection,RTCIceCandidate=Plugin.RTCIceCandidate,RTCSessionDescription=Plugin.RTCSessionDescription}function PeerInitiator(config){function createDataChannel(){if(!isOfferer)return void(peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)});var channel=peer.createDataChannel("RTCDataChannel",{});setChannelEvents(channel)}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}this.extra=config.remoteSdp?config.remoteSdp.extra:config.rtcMultiConnection.extra,this.remoteUserId=config.remoteUserId,this.streams=[],this.channels=[],this.connectionDescription=config.connectionDescription;var that=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};if(Object.observe){var that=this;Object.observe(this.channels,function(changes){changes.forEach(function(change){"add"===change.type&&change.object[change.name].addEventListener("close",function(){delete that.channels[that.channels.indexOf(change.object[change.name])],that.channels=removeNullEntries(that.channels)},!1),("remove"===change.type||"delete"===change.type)&&-1!==that.channels.indexOf(change.object[change.name])&&delete that.channels.indexOf(change.object[change.name]),that.channels=removeNullEntries(that.channels)})})}defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];config.localStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer?(peer=config.peerRef,peer.getLocalStreams().forEach(function(stream){localStreams.forEach(function(localStream,index){stream==localStream&&delete localStreams[index]}),config.removeStreams.forEach(function(streamToRemove,index){stream===streamToRemove&&(peer.removeStream&&peer.removeStream(stream),localStreams.forEach(function(localStream,index){streamToRemove==localStream&&delete localStreams[index]}))})})):peer=new RTCPeerConnection(navigator.onLine?{iceServers:config.iceServers,iceTransports:"all"}:null,config.optionalArgument),peer.onicecandidate=function(event){event.candidate&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex})};var isFirefoxOffered=!isFirefox;config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered&&(isFirefoxOffered=!0),localStreams.forEach(function(localStream){config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream||config.dontAttachLocalStream||peer.addStream(localStream)}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){if(config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:that.extra,userid:that.remoteUserId}),-1!==peer.iceConnectionState.search(/disconnected|closed|failed/gi)){if(peer.firedOnce)return;peer.firedOnce=!0;for(var id in allRemoteStreams)config.onRemoteStreamRemoved(allRemoteStreams[id]);allRemoteStreams={},that.connectionDescription&&config.rtcMultiConnection.userid==that.connectionDescription.sender&&config.rtcMultiConnection.autoReDialOnFailure&&setTimeout(function(){-1!==peer.iceConnectionState.search(/disconnected|closed|failed/gi)&&(config.rtcMultiConnection.rejoin(that.connectionDescription),peer.firedOnce=!1)},5e3)}};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),peer.onaddstream=function(event){var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare&&(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen),event.stream.streamid=event.stream.id,event.stream.stop||(event.stream.stop=function(){fireEvent(event.stream,"ended",event)}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)},peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp){peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),function(){},function(error){config.rtcMultiConnection.enableLogs&&console.error(JSON.stringify(error,null," ")),config.rtcMultiConnection.autoReDialOnFailure&&setTimeout(function(){config.rtcMultiConnection.rejoin(that.connectionDescription)},2e3)})};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),config.enableDataChannels===!0&&createDataChannel(),config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp)),("two-way"==config.session.audio||"two-way"==config.session.video||"two-way"==config.session.screen)&&(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==config.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==config.session.video||"two-way"==config.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),peer[isOfferer?"createOffer":"createAnswer"](function(localSdp){localSdp.sdp=config.processSdp(localSdp.sdp),peer.setLocalDescription(localSdp),config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:config.rtcMultiConnection?config.rtcMultiConnection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})},function(error){config.rtcMultiConnection.enableLogs&&console.error("sdp-error",error),!config.rtcMultiConnection.autoReDialOnFailure||isFirefox||isFirefoxOffered||setTimeout(function(){config.rtcMultiConnection.rejoin(that.connectionDescription)},2e3)},defaults.sdpConstraints),peer.nativeClose=peer.close,peer.close=function(){peer&&"connected"===peer.iceConnectionState&&(peer.nativeClose(),peer=null)},this.peer=peer}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream),options.onGettingLocalMedia(stream,returnBack),stream.addEventListener("ended",function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift())}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{if(isPluginRTC){document.createElement("video");return void Plugin.getUserMedia({audio:!0,video:!0},function(stream){stream.streamid=stream.id||getRandomString(),streaming(stream)},function(error){})}navigator.getMedia=navigator.webkitGetUserMedia||navigator.mozGetUserMedia,"undefined"!=typeof DetectRTC&&(DetectRTC.hasMicrophone||(options.localMediaConstraints.audio=!1),DetectRTC.hasWebcam||(options.localMediaConstraints.video=!1)),navigator.getMedia(options.localMediaConstraints,function(stream){stream.streamid=stream.id||getRandomString(),streaming(stream)},function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isPluginRTC=isSafari||isIE,chromeVersion=(!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),50),matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10)),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop()}),this.getVideoTracks().forEach(function(track){track.stop()}),fireEvent(this,"ended")}),function(){function LoadPluginRTC(){function getPlugin(){return document.getElementById("WebrtcEverywherePluginId")}window.PluginRTC={};var extractPluginObj=function(elt){return elt.isWebRtcPlugin?elt:elt.pluginObj},attachEventListener=function(elt,type,listener,useCapture){var _pluginObj=extractPluginObj(elt);_pluginObj?_pluginObj.bindEventListener(type,listener,useCapture):"undefined"!=typeof elt.addEventListener?elt.addEventListener(type,listener,useCapture):"undefined"!=typeof elt.addEvent&&elt.addEventListener("on"+type,listener,useCapture)},installPlugin=function(){if(!document.getElementById("WebrtcEverywherePluginId")){var pluginObj=document.createElement("object");isIE?pluginObj.setAttribute("classid","CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53"):pluginObj.setAttribute("type","application/webrtc-everywhere"),pluginObj.setAttribute("id","WebrtcEverywherePluginId"),(document.body||document.documentElement).appendChild(pluginObj),pluginObj.setAttribute("width","0"),pluginObj.setAttribute("height","0")}};document.body?installPlugin():(attachEventListener(window,"load",function(){installPlugin()}),attachEventListener(document,"readystatechange",function(){"complete"==document.readyState&&installPlugin()}));var getUserMediaDelayed;window.PluginRTC.getUserMedia=navigator.getUserMedia=function(constraints,successCallback,errorCallback){"complete"!==document.readyState?getUserMediaDelayed||(getUserMediaDelayed=!0,attachEventListener(document,"readystatechange",function(){getUserMediaDelayed&&"complete"==document.readyState&&(getUserMediaDelayed=!1,getPlugin().getUserMedia(constraints,successCallback,errorCallback))})):getPlugin().getUserMedia(constraints,successCallback,errorCallback)},window.PluginRTC.attachMediaStream=function(element,stream){if(element.isWebRtcPlugin)return element.src=stream,element;if("video"===element.nodeName.toLowerCase()){if(!element.pluginObj&&stream){var _pluginObj=document.createElement("object"),_isIE=Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(window,"ActiveXObject")||"ActiveXObject"in window;_isIE?_pluginObj.setAttribute("classid","CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53"):_pluginObj.setAttribute("type","application/webrtc-everywhere"),element.pluginObj=_pluginObj,_pluginObj.setAttribute("className",element.className),_pluginObj.setAttribute("innerHTML",element.innerHTML);var width=element.getAttribute("width"),height=element.getAttribute("height"),bounds=element.getBoundingClientRect();if(width||(width=bounds.right-bounds.left),height||(height=bounds.bottom-bounds.top),"getComputedStyle"in window){var computedStyle=window.getComputedStyle(element,null);width||"auto"==computedStyle.width||"0px"==computedStyle.width||(width=computedStyle.width),height||"auto"==computedStyle.height||"0px"==computedStyle.height||(height=computedStyle.height)}width?_pluginObj.setAttribute("width",width):_pluginObj.setAttribute("autowidth",!0),height?_pluginObj.setAttribute("height",height):_pluginObj.setAttribute("autoheight",!0),(document.body||document.documentElement).appendChild(_pluginObj),element.parentNode&&(element.parentNode.replaceChild(_pluginObj,element), -document.body.appendChild(element),element.style.visibility="hidden")}return element.pluginObj&&(element.pluginObj.bindEventListener("play",function(objvid){element.pluginObj&&(element.pluginObj.getAttribute("autowidth")&&objvid.videoWidth&&element.pluginObj.setAttribute("width",objvid.videoWidth),element.pluginObj.getAttribute("autoheight")&&objvid.videoHeight&&element.pluginObj.setAttribute("height",objvid.videoHeight))}),element.pluginObj.src=stream),element.pluginObj}return"audio"===element.nodeName.toLowerCase()?element:void 0},window.PluginRTC.MediaStreamTrack={};var getSourcesDelayed;window.PluginRTC.MediaStreamTrack.getSources=function(gotSources){"complete"!==document.readyState?getSourcesDelayed||(getSourcesDelayed=!0,attachEventListener(document,"readystatechange",function(){getSourcesDelayed&&"complete"==document.readyState&&(getSourcesDelayed=!1,getPlugin().getSources(gotSources))})):getPlugin().getSources(gotSources)},window.PluginRTC.RTCPeerConnection=function(configuration,constraints){return getPlugin().createPeerConnection(configuration,constraints)},window.PluginRTC.RTCIceCandidate=function(RTCIceCandidateInit){return getPlugin().createIceCandidate(RTCIceCandidateInit)},window.PluginRTC.RTCSessionDescription=function(RTCSessionDescriptionInit){return getPlugin().createSessionDescription(RTCSessionDescriptionInit)},window.onPluginRTCInitialized&&window.onPluginRTCInitialized(window.PluginRTC)}var ua=navigator.userAgent.toLowerCase(),isSafari=-1!=ua.indexOf("safari")&&-1==ua.indexOf("chrome"),isIE=!!(Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(window,"ActiveXObject")||"ActiveXObject"in window);(isSafari||isIE)&&window.addEventListener("load",LoadPluginRTC,!1)}();var RTCPeerConnection,defaults={};"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:(console.error("WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser."),RTCPeerConnection=window.RTCSessionDescription=window.RTCIceCandidate=function(){});var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,Plugin={};"undefined"!=typeof PluginRTC&&onPluginRTCInitialized(PluginRTC);var loadedIceFrame,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isIE=!!document.documentMode,isPluginRTC=isSafari||isIE,OnIceCandidateHandler=function(){function processCandidates(connection,icePair){var candidate=icePair.candidate,iceRestrictions=connection.candidates,stun=iceRestrictions.stun,turn=iceRestrictions.turn;if(isNull(iceRestrictions.reflexive)||(stun=iceRestrictions.reflexive),isNull(iceRestrictions.relay)||(turn=iceRestrictions.relay),(iceRestrictions.host||!candidate.match(/typ host/g))&&(turn||!candidate.match(/typ relay/g))&&(stun||!candidate.match(/typ srflx/g))){var protocol=connection.iceProtocols;if((protocol.udp||!candidate.match(/ udp /g))&&(protocol.tcp||!candidate.match(/ tcp /g)))return connection.enableLogs&&console.debug("Your candidate pairs:",candidate),{candidate:candidate,sdpMid:icePair.sdpMid,sdpMLineIndex:icePair.sdpMLineIndex}}}return{processCandidates:processCandidates}}();("undefined"==typeof window.getExternalIceServers||1==window.getExternalIceServers)&&loadIceFrame(function(externalIceServers){externalIceServers&&externalIceServers.length&&(window.RMCExternalIceServers=externalIceServers,window.iceServersLoadCallback&&"function"==typeof window.iceServersLoadCallback&&window.iceServersLoadCallback(externalIceServers))});var IceServersHandler=function(){function getIceServers(connection){var iceServers=[];return iceServers.push({urls:"stun:stun.l.google.com:19302"}),iceServers.push({urls:"stun:stun.anyfirewall.com:3478"}),iceServers.push({urls:"turn:turn.bistri.com:80",credential:"homeo",username:"homeo"}),iceServers.push({urls:"turn:turn.anyfirewall.com:443?transport=tcp",credential:"webrtc",username:"webrtc"}),window.RMCExternalIceServers?(iceServers=window.RMCExternalIceServers.concat(iceServers),connection.iceServers=iceServers):"undefined"==typeof window.getExternalIceServers||1==window.getExternalIceServers?window.iceServersLoadCallback=function(){iceServers=window.RMCExternalIceServers.concat(iceServers),connection.iceServers=iceServers}:iceServers.push({urls:"turn:turn.anyfirewall.com:443?transport=udp",credential:"webrtc",username:"webrtc"}),iceServers}return{getIceServers:getIceServers}}(),BandwidthHandler=function(){function setBAS(sdp,bandwidth,isScreen){return bandwidth?"undefined"!=typeof isFirefox&&isFirefox?sdp:(isScreen&&(bandwidth.screen?bandwidth.screen<300&&console.warn("It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail."):console.warn("It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.")),bandwidth.screen&&isScreen&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,""),sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+bandwidth.screen+"\r\n")),(bandwidth.audio||bandwidth.video||bandwidth.data)&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,"")),bandwidth.audio&&(sdp=sdp.replace(/a=mid:audio\r\n/g,"a=mid:audio\r\nb=AS:"+bandwidth.audio+"\r\n")),bandwidth.video&&(sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+(isScreen?bandwidth.screen:bandwidth.video)+"\r\n")),sdp):sdp}function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr)}function findLineInRange(sdpLines,startLine,endLine,prefix,substr){for(var realEndLine=-1!==endLine?endLine:sdpLines.length,i=startLine;realEndLine>i;++i)if(0===sdpLines[i].indexOf(prefix)&&(!substr||-1!==sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())))return i;return null}function getCodecPayloadType(sdpLine){var pattern=new RegExp("a=rtpmap:(\\d+) \\w+\\/\\d+"),result=sdpLine.match(pattern);return result&&2===result.length?result[1]:null}function setVideoBitrates(sdp,params){params=params||{};var vp8Payload,xgoogle_min_bitrate=params.min,xgoogle_max_bitrate=params.max,sdpLines=sdp.split("\r\n"),vp8Index=findLine(sdpLines,"a=rtpmap","VP8/90000");if(vp8Index&&(vp8Payload=getCodecPayloadType(sdpLines[vp8Index])),!vp8Payload)return sdp;var rtxPayload,rtxIndex=findLine(sdpLines,"a=rtpmap","rtx/90000");if(rtxIndex&&(rtxPayload=getCodecPayloadType(sdpLines[rtxIndex])),!rtxIndex)return sdp;var rtxFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+rtxPayload.toString());if(null!==rtxFmtpLineIndex){var appendrtxNext="\r\n";appendrtxNext+="a=fmtp:"+vp8Payload+" x-google-min-bitrate="+(xgoogle_min_bitrate||"228")+"; x-google-max-bitrate="+(xgoogle_max_bitrate||"228"),sdpLines[rtxFmtpLineIndex]=sdpLines[rtxFmtpLineIndex].concat(appendrtxNext),sdp=sdpLines.join("\r\n")}return sdp}function setOpusAttributes(sdp,params){params=params||{};var opusPayload,sdpLines=sdp.split("\r\n"),opusIndex=findLine(sdpLines,"a=rtpmap","opus/48000");if(opusIndex&&(opusPayload=getCodecPayloadType(sdpLines[opusIndex])),!opusPayload)return sdp;var opusFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+opusPayload.toString());if(null===opusFmtpLineIndex)return sdp;var appendOpusNext="";return appendOpusNext+="; stereo="+("undefined"!=typeof params.stereo?params.stereo:"1"),appendOpusNext+="; sprop-stereo="+("undefined"!=typeof params["sprop-stereo"]?params["sprop-stereo"]:"1"),"undefined"!=typeof params.maxaveragebitrate&&(appendOpusNext+="; maxaveragebitrate="+(params.maxaveragebitrate||1048576)),"undefined"!=typeof params.maxplaybackrate&&(appendOpusNext+="; maxplaybackrate="+(params.maxplaybackrate||1048576)),"undefined"!=typeof params.cbr&&(appendOpusNext+="; cbr="+("undefined"!=typeof params.cbr?params.cbr:"1")),"undefined"!=typeof params.useinbandfec&&(appendOpusNext+="; useinbandfec="+params.useinbandfec),"undefined"!=typeof params.usedtx&&(appendOpusNext+="; usedtx="+params.usedtx),"undefined"!=typeof params.maxptime&&(appendOpusNext+="\r\na=maxptime:"+params.maxptime),sdpLines[opusFmtpLineIndex]=sdpLines[opusFmtpLineIndex].concat(appendOpusNext),sdp=sdpLines.join("\r\n")}return{setApplicationSpecificBandwidth:function(sdp,bandwidth,isScreen){return setBAS(sdp,bandwidth,isScreen)},setVideoBitrates:function(sdp,params){return setVideoBitrates(sdp,params)},setOpusAttributes:function(sdp,params){return setOpusAttributes(sdp,params)}}}(),currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]},StreamsHandler=function(){function handleType(type){return type?"string"==typeof type||"undefined"==typeof type?type:type.audio&&type.video?null:type.audio?"audio":type.video?"video":void 0:void 0}function setHandlers(stream,syncAction,connection){function graduallyIncreaseVolume(){if(connection.streamEvents[stream.streamid].mediaElement){var mediaElement=connection.streamEvents[stream.streamid].mediaElement;mediaElement.volume=0,afterEach(200,5,function(){mediaElement.volume+=.2})}}("undefined"==typeof syncAction||1==syncAction)&&stream.addEventListener("ended",function(){StreamsHandler.onSyncNeeded(stream.streamid,"ended")},!1),stream.mute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!1,connection.streamEvents[stream.streamid].isAudioMuted=!0}),("undefined"==typeof type||"video"==type)&&stream.getVideoTracks().forEach(function(track){track.enabled=!1}),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"mute",type),connection.streamEvents[stream.streamid].muteType=type,fireEvent(stream,"mute",type)},stream.unmute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),graduallyIncreaseVolume(),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!0,connection.streamEvents[stream.streamid].isAudioMuted=!1}),("undefined"==typeof type||"video"==type)&&(stream.getVideoTracks().forEach(function(track){track.enabled=!0}),"undefined"!=typeof type&&"video"==type&&connection.streamEvents[stream.streamid].isAudioMuted&&!function looper(times){times||(times=0),times++,100>times&&connection.streamEvents[stream.streamid].isAudioMuted&&(stream.mute("audio"),setTimeout(function(){looper(times)},50))}()),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"unmute",type),connection.streamEvents[stream.streamid].unmuteType=type,fireEvent(stream,"unmute",type)}}function afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes){startedTimes=(startedTimes||0)+1,startedTimes>=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();!function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("MSIE"),browserName="IE",fullVersion=nAgt.substring(verOffset+5)):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10).toString()),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return match&&parseInt(match[1],10)>=10?!0:!1}function detectPrivateMode(callback){var isPrivate;if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){console.log(e),isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test")}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState?!0:!1},function(isTimeout){isTimeout||(isPrivate=db.result?!1:!0)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}retry(function(){return"undefined"!=typeof isPrivate?!0:!1},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){var unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(var id in clientStrings){var cs=clientStrings[id];if(cs.r.test(nAgt)){os=cs.s;break}}var osVersion=unknown;switch(/Windows/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1],os="Windows"),os){case"Mac OS X":osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];break;case"Android":osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1];break;case"iOS":osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3])}return{osName:os,osVersion:osVersion}}function DetectLocalIPAddress(callback){DetectRTC.isWebRTCSupported&&(DetectRTC.isORTCSupported||getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)}))}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(!match)return void console.warn("Could not match IP address in",candidate);var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}if(RTCPeerConnection){var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}}function checkDeviceSupport(callback){if(canEnumerate){if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];"audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput");var skip;MediaDevices.forEach(function(d){d.id===device.id&&d.kind===device.kind&&(skip=!0)}),skip||(device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.label="Please invoke getUserMedia once.","https:"!==location.protocol&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,-1===audioInputDevices.indexOf(device)&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,-1===audioOutputDevices.indexOf(device)&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,-1===videoInputDevices.indexOf(device)&&videoInputDevices.push(device)),-1===MediaDevices.indexOf(device)&&MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}}var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:"Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45"};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode&&!isEdge,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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version";if(isMobile.any())osName=isMobile.getOsName();else{var osInfo=detectDesktopOS();osName=osInfo.osName,osVersion=osInfo.osVersion}var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;checkDeviceSupport();var DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),"https:"!==location.protocol&&(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&"https:"!==location.protocol&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}(),function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:window.screen.width>1920?window.screen.width:1920,maxHeight:window.screen.height>1080?window.screen.height:1080},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window"}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}(),function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:window.screen.width>1920?window.screen.width:1920,maxHeight:window.screen.height>1080?window.screen.height:1080},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}if(-1!==document.domain.indexOf("webrtc-experiment.com")){window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window"}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}}();var TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText); -}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(-1!==progress.position){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(!chunk.remoteUserId||(helper=progressHelper[chunk.uuid][chunk.remoteUserId]))&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exists.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;-1!=file.type.indexOf("image")?div.innerHTML='Download '+file.name+'
':div.innerHTML='Download '+file.name+'
'}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"===response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}}}return{handle:handle}}();window.RTCMultiConnection=RTCMultiConnection}(); \ No newline at end of file +"use strict";!function(){function RTCMultiConnection(roomid,forceOptions){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(socket)return void(connectCallback&&connectCallback(socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}socket=new SocketConnection(connection,function(s){socket=s,connectCallback&&connectCallback(socket)})}function beforeUnload(shiftModerationControlOnLeave,dontCloseSocket){connection.closeBeforeUnload&&(connection.isInitiator===!0&&connection.dontMakeMeModerator(),connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.broadcasters=[],connection.isInitiator=!1)}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}function keepNextBroadcasterOnServer(){if(connection.isInitiator&&!connection.session.oneway&&!connection.session.broadcast&&"many-to-many"===connection.direction){var firstBroadcaster=connection.broadcasters[0],otherBroadcasters=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==firstBroadcaster&&otherBroadcasters.push(broadcaster)}),connection.autoCloseEntireSession||connection.shiftModerationControl(firstBroadcaster,otherBroadcasters,!0)}}forceOptions=forceOptions||{};var connection=this;connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection);mPeer.onGettingLocalMedia=function(stream){stream.type="local",connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,-1===connection.attachStreams.indexOf(stream)&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream),isAudioMuted:!0},setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){stream.type="remote",connection.setStreamEndHandler(stream,"remote-stream"),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream)},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){connectSocket(function(){socket.emit(connection.socketMessageEvent,"password"in message?message:{remoteUserId:message.remoteUserId||remoteUserId,message:message,sender:connection.userid},callback||function(){})})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){socket&&socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.broadcasters=[],connection.socketOptions={transport:"polling"};var socket;connection.openOrJoin=function(localUserid,password){connection.checkPresence(localUserid,function(isRoomExists,roomid){if("function"==typeof password&&(password(isRoomExists,roomid),password=null),isRoomExists){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:password||!1};return void mPeer.onNegotiationNeeded(connectionDescription)}connection.userid;connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",socket.emit("changed-uuid",connection.userid),password&&socket.emit("set-password",password),connection.isInitiator=!0,isData(connection.session)||connection.captureUserMedia()})},connection.open=function(localUserid,isPublicModerator){connection.userid;return connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connection.isInitiator=!0,connectSocket(function(){socket.emit("changed-uuid",connection.userid),1==isPublicModerator&&connection.becomePublicModerator()}),isData(connection.session)?void("function"==typeof isPublicModerator&&isPublicModerator()):void connection.captureUserMedia("function"==typeof isPublicModerator?isPublicModerator:null)},connection.becomePublicModerator=function(){connection.isInitiator&&socket.emit("become-a-public-moderator")},connection.dontMakeMeModerator=function(){socket.emit("dont-make-me-moderator")},connection.deletePeer=function(remoteUserId){if(remoteUserId){if(connection.onleave({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}}),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}if(-1!==connection.broadcasters.indexOf(remoteUserId)){var newArray=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==remoteUserId&&newArray.push(broadcaster)}),connection.broadcasters=newArray,keepNextBroadcasterOnServer()}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=connection.connect=function(remoteUserId,options){connection.sessionid=(remoteUserId?remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId:!1)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{},"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:!1};return connectSocket(function(){connection.peers[connection.sessionid]||mPeer.onNegotiationNeeded(connectionDescription)}),connectionDescription},connection.connectWithAllParticipants=function(remoteUserId){mPeer.onNegotiationNeeded("connectWithAllParticipants",remoteUserId||connection.sessionid)},connection.removeFromBroadcastersList=function(remoteUserId){mPeer.onNegotiationNeeded("removeFromBroadcastersList",remoteUserId||connection.sessionid),connection.peers.getAllParticipants(remoteUserId||connection.sessionid).forEach(function(participant){mPeer.onNegotiationNeeded("dropPeerConnection",participant),connection.deletePeer(participant)}),connection.attachStreams.forEach(function(stream){stream.stop()})},connection.getUserMedia=connection.captureUserMedia=function(callback,session){function invokeGetUserMedia(localMediaConstraints,getUserMedia_callback){var isScreen=!1;localMediaConstraints&&(isScreen=localMediaConstraints.isScreen,delete localMediaConstraints.isScreen),getUserMediaHandler({onGettingLocalMedia:function(stream){return stream.isAudio=stream.isVideo=stream.isScreen=!1,isScreen?stream.isScreen=!0:session.audio&&session.video?stream.isVideo=!0:session.audio&&(stream.isAudio=!0),mPeer.onGettingLocalMedia(stream),getUserMedia_callback?getUserMedia_callback():void(callback&&callback(stream))},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,getUserMedia_callback)):void(callback&&callback())},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return session=session||connection.session,connection.dontCaptureUserMedia||isData(session)?void(callback&&callback()):void((session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){if(error)throw error;invokeGetUserMedia({video:screen_constraints,isScreen:!0},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",beforeUnload,!1),connection.userid=getRandomString(),connection.changeUserId=function(newUserId,callback){connection.userid=newUserId||getRandomString(),socket.emit("changed-uuid",connection.userid,callback||function(){})},connection.extra={},connection.attachStreams=[],connection.removeStreams=[],connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:512,audio:128,video:512},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return isMobileDevice||isFirefox?sdp:(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen),sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024}),sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3}),"VP9"===connection.codecs.video&&(sdp=CodecsHandler.preferVP9(sdp)),"H264"===connection.codecs.video&&(sdp=CodecsHandler.removeVPX(sdp)),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),sdp)},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]},video:{mandatory:{},optional:[{bandwidth:8*connection.bandwidth.audio*1024||1048576},{googLeakyBucket:!0},{facingMode:"user"}]}},isFirefox&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if(isFirefox)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),connection.mediaConstraints.audio.optional||(connection.mediaConstraints.audio.optional=[]);var optional=[{sourceId:lastAudioDevice.id}];connection.mediaConstraints.audio.optional=optional.concat(connection.mediaConstraints.audio.optional)}if(lastVideoDevice){if(isFirefox)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),connection.mediaConstraints.video.optional||(connection.mediaConstraints.video.optional=[]);var optional=[{sourceId:lastVideoDevice.id}];connection.mediaConstraints.video.optional=optional.concat(connection.mediaConstraints.video.optional)}}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){beforeUnload(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&console.info("Entire session is closed: ",event.sessionid,event.extra)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild),e.mediaElement.play(),setTimeout(function(){e.mediaElement.play()},5e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?void(-1===connection.removeStreams.indexOf(stream)&&(connection.removeStreams.push(stream),connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)}))):void console.warn("No such stream exists.",streamid)},connection.addStream=function(session,remoteUserId){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints?localMediaConstraints.video:connection.mediaConstraints;return videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream),session.streamCallback&&session.streamCallback(stream),callback?callback():void connection.renegotiate(remoteUserId)},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,callback)):callback?callback():void connection.renegotiate(remoteUserId)},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return session.getAudioTracks?(-1===connection.attachStreams.indexOf(session)&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((!session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){return mPeer.onGettingLocalMedia(stream),callback?callback():void connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,callback)):void(callback&&callback())},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return session.getVideoTracks().length&&replaceTrack(session.getVideoTracks()[0],remoteUserId,!0),void(session.getAudioTracks().length&&replaceTrack(session.getAudioTracks()[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(!session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia())},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){stream&&stream.addEventListener&&(isRemote=!!isRemote,stream.alreadySetEndHandler||(stream.alreadySetEndHandler=!0,stream.addEventListener("ended",function(){stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),isRemote||(delete connection.attachStreams[connection.attachStreams.indexOf(stream)],-1===connection.removeStreams.indexOf(stream)&&connection.removeStreams.push(stream),connection.attachStreams=removeNullEntries(connection.attachStreams),connection.removeStreams=removeNullEntries(connection.removeStreams));var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:isRemote?"remote":"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),(streamEvent.userid!==connection.userid||"remote"!==streamEvent.type)&&(connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)))},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.addNewBroadcaster=function(broadcasterId,userPreferences){connection.broadcasters.length&&setTimeout(function(){mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId,userPreferences,connection.broadcasters.join("|-,-|"))},1e4),connection.session.oneway||connection.session.broadcast||"many-to-many"!==connection.direction||-1!==connection.broadcasters.indexOf(broadcasterId)||(connection.broadcasters.push(broadcasterId),keepNextBroadcasterOnServer())},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},connection.onShiftedModerationControl=function(sender,existingBroadcasters){connection.acceptModerationControl(sender,existingBroadcasters)},connection.acceptModerationControl=function(sender,existingBroadcasters){connection.isInitiator=!0,connection.broadcasters=existingBroadcasters,connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({changedUUID:sender,oldUUID:connection.userid,newUUID:sender},participant)}),connection.userid=sender,socket.emit("changed-uuid",connection.userid)},connection.shiftModerationControl=function(remoteUserId,existingBroadcasters,firedOnLeave){mPeer.onNegotiationNeeded({shiftedModerationControl:!0,broadcasters:existingBroadcasters,firedOnLeave:!!firedOnLeave},remoteUserId)},"undefined"!=typeof StreamsHandler&&(connection.StreamsHandler=StreamsHandler),connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.selectSingleFile(callback)},connection.getPublicModerators=function(userIdStartsWith,callback){"function"==typeof userIdStartsWith&&(callback=userIdStartsWith),connectSocket(function(socket){socket.emit("get-public-moderators","string"==typeof userIdStartsWith?userIdStartsWith:"",callback)})},connection.onmute=function(e){e.mediaElement&&("both"===e.muteType||"video"===e.muteType?(e.mediaElement.src=null,e.mediaElement.pause(),e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"):"audio"===e.muteType&&(e.mediaElement.muted=!0))},connection.onunmute=function(e){e.mediaElement&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.src=URL.createObjectURL(e.stream),e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.onJoinWithPassword=function(remoteUserId){console.warn(remoteUserId,"is password protected. Please join with password.")},connection.onInvalidPassword=function(remoteUserId,oldPassword){console.warn(remoteUserId,"is password protected. Please join with valid password. Your old password",oldPassword,"is wrong.")},connection.onPasswordMaxTriesOver=function(remoteUserId){console.warn(remoteUserId,"is password protected. Your max password tries exceeded the limit.")},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){socket&&("undefined"!=typeof socket.disconnect&&socket.disconnect(),socket=null)},connection.getSocket=function(callback){return socket?callback&&callback(socket):connectSocket(callback),socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){if(!options){var firstStream;for(var str in connection.streamEvents)-1!==skipStreams.indexOf(str)||firstStream||(firstStream=connection.streamEvents[str]);return firstStream}},selectAll:function(){}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=65e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(remoteUserId,callback){mPeer.onNegotiationNeeded({detectPresence:!0,userid:(remoteUserId||connection.sessionid)+""},"system",callback)},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.getScreenConstraints=function(callback){getScreenConstraints(function(error,screen_constraints){error||(screen_constraints=connection.modifyScreenConstraints(screen_constraints),callback(error,screen_constraints))})},connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&-1!==state.iceConnectionState.search(/closed|failed/gi)&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=-1!==navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g),connection.isLowBandwidth)){if(connection.bandwidth={audio:30,video:30,screen:30},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt); +}),connection.mediaConstraints.audio.optional=newArray}if(connection.mediaConstraints.video&&connection.mediaConstraints.video.optional.length){var newArray=[];connection.mediaConstraints.video.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId){if(!remoteUserId)throw"remoteUserId is required.";return connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",yourNewUserId),connection.join(useridAlreadyTaken)},connection.trickleIce=!0}function SocketConnection(connection,connectCallback){var parameters="";parameters+="?userid="+connection.userid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2));var socket;try{socket=io((connection.socketURL||"/")+parameters)}catch(e){socket=io.connect((connection.socketURL||"/")+parameters,connection.socketOptions)}var mPeer=connection.multiPeersHandler;return socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}))}),socket.on(connection.socketMessageEvent,function(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra})),message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action||"stream-removed"===action)return void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void stream.stream[action](type)}if("connectWithAllParticipants"===message.message)return-1===connection.broadcasters.indexOf(message.sender)&&connection.broadcasters.push(message.sender),void mPeer.onNegotiationNeeded({allParticipants:connection.getAllParticipants(message.sender)},message.sender);if("removeFromBroadcastersList"===message.message)return void(-1!==connection.broadcasters.indexOf(message.sender)&&(delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)],connection.broadcasters=removeNullEntries(connection.broadcasters)));if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return-1===message.message.allParticipants.indexOf(message.sender)&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if((message.message.readyForOffer||message.message.addMeAsBroadcaster)&&connection.addNewBroadcaster(message.sender),message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&connection.deletePeer(message.sender);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){("undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction)&&connection.addNewBroadcaster(message.sender,userPreferences),(connection.session.oneway||"one-way"===connection.direction||isData(connection.session))&&connection.addNewBroadcaster(message.sender,userPreferences)}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.shiftedModerationControl?void connection.onShiftedModerationControl(message.sender,message.message.broadcasters):(message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender))}}),socket.on("user-left",function(userid){onUserLeft(userid),connection.onUserStatusChanged({userid:userid,status:"offline",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}}),connection.onleave({userid:userid,extra:{}})}),socket.on("connect",function(){connection.enableLogs&&console.info("socket.io connection is opened."),socket.emit("extra-data-updated",connection.extra),connectCallback&&connectCallback(socket)}),socket.on("disconnect",function(){connection.enableLogs&&(console.info("socket.io connection is closed"),console.warn("socket.io reconnecting"))}),socket.on("join-with-password",function(remoteUserId){connection.onJoinWithPassword(remoteUserId)}),socket.on("invalid-password",function(remoteUserId,oldPassword){connection.onInvalidPassword(remoteUserId,oldPassword)}),socket.on("password-max-tries-over",function(remoteUserId){connection.onPasswordMaxTriesOver(remoteUserId)}),socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),socket.on("closed-entire-session",function(sessionid,extra){connection.leave(),connection.onEntireSessionClosed({sessionid:sessionid,userid:sessionid,extra:extra})}),socket.on("userid-already-taken",function(useridAlreadyTaken,yourNewUserId){connection.isInitiator=!1,connection.userid=yourNewUserId,connection.onUserIdAlreadyTaken(useridAlreadyTaken,yourNewUserId)}),socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),socket}function MultiPeers(connection){function invokeGetUserMedia(mediaConstraints,message,remoteUserId){getUserMediaHandler({onGettingLocalMedia:function(localStream){self.onGettingLocalMedia(localStream);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},onLocalMediaError:function(error,constraints){return self.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,message,remoteUserId)):void self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},localMediaConstraints:mediaConstraints})}function initFileBufferReader(){fbr=new FileBufferReader,fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},fbr.onBegin=function(file){connection.onFileStart(file)},fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)-1==skipPeers.indexOf(peer)&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)-1==skipPeers.indexOf(peer)&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)-1==skipPeers.indexOf(peer)&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return void remoteUser.channels.forEach(function(channel){channel.send(data)})}this.getAllParticipants().forEach(function(participant){that[participant].channels.forEach(function(channel){channel.send(data)})})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,userid:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void fbr.getNextChunk(message.uuid,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):void fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){if(connection.peers[remoteUserId].streams.push(stream),isPluginRTC&&window.PluginRTC){var mediaElement=document.createElement("video"),body=connection.videosContainer;return body.insertBefore(mediaElement,body.firstChild),void setTimeout(function(){window.PluginRTC.attachMediaStream(mediaElement,stream)},3e3)}self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),-1!==states.iceConnectionState.search(/closed|failed/gi)&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences,remoteUserId);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{},remoteUserId);var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])return void(connection.enableLogs&&console.error("This peer ("+remoteUserId+") does not exists. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exists.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&rtpSender.track instanceof VideoStreamTrack&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),!isVideoTrack&&rtpSender.track instanceof AudioStreamTrack&&(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){if(connection.attachStreams.length||connection.dontCaptureUserMedia){var streamsToShare={};return connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,void self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}var localMediaConstraints={},userPreferences=message.userPreferences;userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video),invokeGetUserMedia(localMediaConstraints,message,remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.connectNewParticipantWithAllBroadcasters=function(newParticipantId,userPreferences,broadcastersList){if(broadcastersList=broadcastersList.split("|-,-|"),broadcastersList.length){var firstBroadcaster=broadcastersList[0];self.onNegotiationNeeded({newParticipant:newParticipantId,userPreferences:userPreferences||!1},firstBroadcaster),delete broadcastersList[0];var array=[];broadcastersList.forEach(function(broadcaster){broadcaster&&array.push(broadcaster)}),setTimeout(function(){self.connectNewParticipantWithAllBroadcasters(newParticipantId,userPreferences,array.join("|-,-|"))},1e4)}},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)};var fbr;this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:isFirefox?15e3:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){connection.peers[remoteUserId].channels.length||(connection.peers[remoteUserId].channels.push(channel),connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel}))},this.onPeerStateChanged=function(state){connection.onPeerStateChanged(state)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]},this.isPluginRTC=connection.isPluginRTC=isPluginRTC}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&-1===navigator.userAgent.indexOf("Safari")){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;l>i;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function getRMCMediaElement(stream,callback,connection){var isAudioOnly=!1;stream.getVideoTracks&&!stream.getVideoTracks().length&&(isAudioOnly=!0);var mediaElement=document.createElement(isAudioOnly?"audio":"video");return isPluginRTC&&window.PluginRTC?(connection.videosContainer.insertBefore(mediaElement,connection.videosContainer.firstChild),void setTimeout(function(){window.PluginRTC.attachMediaStream(mediaElement,stream),callback(mediaElement)},1e3)):(mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:window.URL.createObjectURL(stream),mediaElement.controls=!0,isFirefox&&mediaElement.addEventListener("ended",function(){if(currentUserMediaRequest.remove(stream.idInstance),"local"===stream.type){StreamsHandler.onSyncNeeded(stream.streamid,"ended"),connection.attachStreams.forEach(function(aStream,idx){stream.streamid===aStream.streamid&&delete connection.attachStreams[idx]});var newStreamsArray=[];connection.attachStreams.forEach(function(aStream){aStream&&newStreamsArray.push(aStream)}),connection.attachStreams=newStreamsArray;var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent)return void connection.onstreamended(streamEvent);this.parentNode&&this.parentNode.removeChild(this)}},!1),mediaElement.play(),void callback(mediaElement))}function listenEventHandler(eventName,eventHandler){window.removeEventListener(eventName,eventHandler),window.addEventListener(eventName,eventHandler,!1)}function removeNullEntries(array){var newArray=[];return array.forEach(function(item){item&&newArray.push(item)}),newArray}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function setCordovaAPIs(){if("iOS"===DetectRTC.osName&&"undefined"!=typeof cordova&&"undefined"!=typeof cordova.plugins&&"undefined"!=typeof cordova.plugins.iosrtc){var iosrtc=cordova.plugins.iosrtc;window.webkitRTCPeerConnection=iosrtc.RTCPeerConnection,window.RTCSessionDescription=iosrtc.RTCSessionDescription,window.RTCIceCandidate=iosrtc.RTCIceCandidate,window.MediaStream=iosrtc.MediaStream,window.MediaStreamTrack=iosrtc.MediaStreamTrack,navigator.getUserMedia=navigator.webkitGetUserMedia=iosrtc.getUserMedia,iosrtc.debug.enable("iosrtc*"),iosrtc.registerGlobals()}}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function PeerInitiator(config){function createDataChannel(){if(!isOfferer)return void(peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)});var channel=peer.createDataChannel("RTCDataChannel",{});setChannelEvents(channel)}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}if(!RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra,this.userid=config.userid,this.streams=[],this.channels=[],this.connectionDescription=config.connectionDescription;var that=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer?(peer=config.peerRef,peer.getLocalStreams().forEach(function(stream){localStreams.forEach(function(localStream,index){stream==localStream&&delete localStreams[index]}),connection.removeStreams.forEach(function(streamToRemove,index){stream===streamToRemove&&(stream=connection.beforeRemovingStream(stream),stream&&peer.removeStream&&peer.removeStream(stream),localStreams.forEach(function(localStream,index){streamToRemove==localStream&&delete localStreams[index]}))})})):peer=new RTCPeerConnection(navigator.onLine?{iceServers:connection.iceServers,iceTransports:"all"}:null,window.PluginRTC?null:connection.optionalArgument),"Firefox"===connection.DetectRTC.browser.name&&(peer.removeStream=function(stream){stream.mute(),connection.StreamsHandler.onSyncNeeded(stream.streamid,"stream-removed")}),peer.onicecandidate=function(event){if(event.candidate)connection.trickleIce&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex});else if(!connection.trickleIce){var localSdp=peer.localDescription;config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})}};var isFirefoxOffered=!isFirefox;config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered&&(isFirefoxOffered=!0),localStreams.forEach(function(localStream){config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream||config.dontAttachLocalStream||(localStream=connection.beforeAddingStream(localStream),localStream&&peer.addStream(localStream))}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=that.extra;connection.peers[that.userid]&&(extra=connection.peers[that.userid].extra||extra),peer&&config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:that.userid})};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),peer.onaddstream=function(event){var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare&&(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen),event.stream.streamid=event.stream.id,event.stream.stop||(event.stream.stop=function(){isFirefox&&fireEvent(this,"ended")}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)},peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp){remoteSdp.sdp=connection.processSdp(remoteSdp.sdp),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),function(){},function(error){connection.enableLogs&&console.error(JSON.stringify(error,null," "),"\n",remoteSdp.type,remoteSdp.sdp)})};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),connection.session.data===!0&&createDataChannel(),config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp)),("two-way"==connection.session.audio||"two-way"==connection.session.video||"two-way"==connection.session.screen)&&(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),peer[isOfferer?"createOffer":"createAnswer"](function(localSdp){localSdp.sdp=connection.processSdp(localSdp.sdp),peer.setLocalDescription(localSdp),connection.trickleIce&&config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})},function(error){connection.enableLogs&&console.error("sdp-error",error)},defaults.sdpConstraints),peer.nativeClose=peer.close, +peer.close=function(){if(peer){try{-1===peer.iceConnectionState.search(/closed|failed/gi)&&peer.getRemoteStreams().forEach(function(stream){stream.stop()}),peer.nativeClose()}catch(e){}peer=null,that.peer=null}},this.peer=peer}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function getSTUNObj(stunStr){var urlsParam="urls";isPluginRTC&&(urlsParam="url");var obj={};return obj[urlsParam]=stunStr,obj}function getTURNObj(turnStr,username,credential){var urlsParam="urls";isPluginRTC&&(urlsParam="url");var obj={username:username,credential:credential};return obj[urlsParam]=turnStr,obj}function getExtenralIceFormatted(){var iceServers;return window.RMCExternalIceServers.forEach(function(ice){ice.urls||(ice.urls=ice.url),-1!==ice.urls.search("stun|stuns")&&iceServers.push(getSTUNObj(ice.urls)),-1!==ice.urls.search("turn|turns")&&iceServers.push(getTURNObj(ice.urls,ice.username,ice.credential))}),iceServers}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream),options.onGettingLocalMedia(stream,returnBack),stream.addEventListener("ended",function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift())}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{if(isPluginRTC&&window.PluginRTC){document.createElement("video");return void window.PluginRTC.getUserMedia({audio:!0,video:!0},function(stream){stream.streamid=stream.id||getRandomString(),streaming(stream)},function(error){})}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);"undefined"!=typeof cordova&&(isMobileDevice=!0,isChrome=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0,isChrome=!0);var isPluginRTC=!isMobileDevice&&(isSafari||isIE);isPluginRTC&&"undefined"!=typeof URL&&(URL.createObjectURL=function(){});var chromeVersion=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),50),matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10)),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&tracks.push(track)}),tracks}),"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()})})),function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("MSIE"),browserName="IE",fullVersion=nAgt.substring(verOffset+5)):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10).toString()),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return match&&parseInt(match[1],10)>=10?!0:!1}function detectPrivateMode(callback){var isPrivate;if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){console.log(e),isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test")}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState?!0:!1},function(isTimeout){isTimeout||(isPrivate=db.result?!1:!0)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}retry(function(){return"undefined"!=typeof isPrivate?!0:!1},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){var unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(var id in clientStrings){var cs=clientStrings[id];if(cs.r.test(nAgt)){os=cs.s;break}}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function DetectLocalIPAddress(callback){DetectRTC.isWebRTCSupported&&(DetectRTC.isORTCSupported||getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)}))}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(!match)return void console.warn("Could not match IP address in",candidate);var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}if(RTCPeerConnection){var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}}function checkDeviceSupport(callback){if(canEnumerate){if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];"audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput");var skip;MediaDevices.forEach(function(d){d.id===device.id&&d.kind===device.kind&&(skip=!0)}),skip||(device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.label="Please invoke getUserMedia once.","https:"!==location.protocol&&document.domain.search&&-1===document.domain.search(/localhost|127.0./g)&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,-1===audioInputDevices.indexOf(device)&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,-1===audioOutputDevices.indexOf(device)&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,-1===videoInputDevices.indexOf(device)&&videoInputDevices.push(device)),-1===MediaDevices.indexOf(device)&&MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}}var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:"Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45"};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode&&!isEdge,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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version";if(isMobile.any())osName=isMobile.getOsName();else{var osInfo=detectDesktopOS();osName=osInfo.osName,osVersion=osInfo.osVersion}var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;checkDeviceSupport();var DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),"https:"!==location.protocol&&(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&"https:"!==location.protocol&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}(),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection&&(RTCPeerConnection=window.RTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack;window.onPluginRTCInitialized=function(){MediaStreamTrack=window.PluginRTC.MediaStreamTrack,RTCPeerConnection=window.PluginRTC.RTCPeerConnection,RTCIceCandidate=window.PluginRTC.RTCIceCandidate,RTCSessionDescription=window.PluginRTC.RTCSessionDescription},"undefined"!=typeof window.PluginRTC&&window.onPluginRTCInitialized();var CodecsHandler=function(){function removeVPX(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtpmap:100 VP8/90000\r\n",""),sdp=sdp.replace("a=rtpmap:101 VP9/90000\r\n",""),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace("a=rtcp-fb:120 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 ccm fir\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 ccm fir\r\n","")}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;ii;++i)if(0===sdpLines[i].indexOf(prefix)&&(!substr||-1!==sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())))return i;return null}function getCodecPayloadType(sdpLine){var pattern=new RegExp("a=rtpmap:(\\d+) \\w+\\/\\d+"),result=sdpLine.match(pattern);return result&&2===result.length?result[1]:null}function setVideoBitrates(sdp,params){if(isMobileDevice)return sdp;params=params||{};var vp8Payload,xgoogle_min_bitrate=params.min,xgoogle_max_bitrate=params.max,sdpLines=sdp.split("\r\n"),vp8Index=findLine(sdpLines,"a=rtpmap","VP8/90000");if(vp8Index&&(vp8Payload=getCodecPayloadType(sdpLines[vp8Index])),!vp8Payload)return sdp;var rtxPayload,rtxIndex=findLine(sdpLines,"a=rtpmap","rtx/90000");if(rtxIndex&&(rtxPayload=getCodecPayloadType(sdpLines[rtxIndex])),!rtxIndex)return sdp;var rtxFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+rtxPayload.toString());if(null!==rtxFmtpLineIndex){var appendrtxNext="\r\n";appendrtxNext+="a=fmtp:"+vp8Payload+" x-google-min-bitrate="+(xgoogle_min_bitrate||"228")+"; x-google-max-bitrate="+(xgoogle_max_bitrate||"228"),sdpLines[rtxFmtpLineIndex]=sdpLines[rtxFmtpLineIndex].concat(appendrtxNext),sdp=sdpLines.join("\r\n")}return sdp}function setOpusAttributes(sdp,params){if(isMobileDevice)return sdp;params=params||{};var opusPayload,sdpLines=sdp.split("\r\n"),opusIndex=findLine(sdpLines,"a=rtpmap","opus/48000");if(opusIndex&&(opusPayload=getCodecPayloadType(sdpLines[opusIndex])),!opusPayload)return sdp;var opusFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+opusPayload.toString());if(null===opusFmtpLineIndex)return sdp;var appendOpusNext="";return appendOpusNext+="; stereo="+("undefined"!=typeof params.stereo?params.stereo:"1"),appendOpusNext+="; sprop-stereo="+("undefined"!=typeof params["sprop-stereo"]?params["sprop-stereo"]:"1"),"undefined"!=typeof params.maxaveragebitrate&&(appendOpusNext+="; maxaveragebitrate="+(params.maxaveragebitrate||1048576)),"undefined"!=typeof params.maxplaybackrate&&(appendOpusNext+="; maxplaybackrate="+(params.maxplaybackrate||1048576)),"undefined"!=typeof params.cbr&&(appendOpusNext+="; cbr="+("undefined"!=typeof params.cbr?params.cbr:"1")),"undefined"!=typeof params.useinbandfec&&(appendOpusNext+="; useinbandfec="+params.useinbandfec),"undefined"!=typeof params.usedtx&&(appendOpusNext+="; usedtx="+params.usedtx),"undefined"!=typeof params.maxptime&&(appendOpusNext+="\r\na=maxptime:"+params.maxptime),sdpLines[opusFmtpLineIndex]=sdpLines[opusFmtpLineIndex].concat(appendOpusNext),sdp=sdpLines.join("\r\n")}function preferVP9(sdp){return-1===sdp.indexOf("SAVPF 100 101")||-1===sdp.indexOf("VP9/90000")?sdp:sdp.replace("SAVPF 100 101","SAVPF 101 100")}var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);return"undefined"!=typeof cordova&&(isMobileDevice=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0),{removeVPX:removeVPX,disableNACK:disableNACK,prioritize:prioritize,removeNonG722:removeNonG722,setApplicationSpecificBandwidth:function(sdp,bandwidth,isScreen){return setBAS(sdp,bandwidth,isScreen)},setVideoBitrates:function(sdp,params){return setVideoBitrates(sdp,params)},setOpusAttributes:function(sdp,params){return setOpusAttributes(sdp,params)},preferVP9:preferVP9}}();window.BandwidthHandler=CodecsHandler;var loadedIceFrame,OnIceCandidateHandler=function(){function processCandidates(connection,icePair){var candidate=icePair.candidate,iceRestrictions=connection.candidates,stun=iceRestrictions.stun,turn=iceRestrictions.turn;if(isNull(iceRestrictions.reflexive)||(stun=iceRestrictions.reflexive),isNull(iceRestrictions.relay)||(turn=iceRestrictions.relay),(iceRestrictions.host||!candidate.match(/typ host/g))&&(turn||!candidate.match(/typ relay/g))&&(stun||!candidate.match(/typ srflx/g))){var protocol=connection.iceProtocols;if((protocol.udp||!candidate.match(/ udp /g))&&(protocol.tcp||!candidate.match(/ tcp /g)))return connection.enableLogs&&console.debug("Your candidate pairs:",candidate),{candidate:candidate,sdpMid:icePair.sdpMid,sdpMLineIndex:icePair.sdpMLineIndex}}}return{processCandidates:processCandidates}}();"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&loadIceFrame(function(externalIceServers){externalIceServers&&externalIceServers.length&&(window.RMCExternalIceServers=externalIceServers,window.iceServersLoadCallback&&"function"==typeof window.iceServersLoadCallback&&window.iceServersLoadCallback(externalIceServers))});var IceServersHandler=function(){function getIceServers(connection){var iceServers=[];return iceServers.push(getSTUNObj("stun:stun.l.google.com:19302")),iceServers.push(getTURNObj("turn:turn.bistri.com:80","homeo","homeo")),iceServers.push(getTURNObj("turn:turn.anyfirewall.com:443","webrtc","webrtc")),window.RMCExternalIceServers?iceServers=iceServers.concat(getExtenralIceFormatted()):"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&(connection.iceServers=iceServers,window.iceServersLoadCallback=function(){connection.iceServers=connection.iceServers.concat(getExtenralIceFormatted())}),iceServers}return{getIceServers:getIceServers}}(),getUserMedia=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils=window.webrtcUtils||{}; +if(webrtcUtils.enableLogs||(webrtcUtils.enableLogs=!0),webrtcUtils.log||(webrtcUtils.log=function(){webrtcUtils.enableLogs&&("undefined"!=typeof module||"function"==typeof require&&"function"==typeof define||console.log.apply(console,arguments))}),webrtcUtils.extractVersion||(webrtcUtils.extractVersion=function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10)}),"object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=stream?URL.createObjectURL(stream):null)}}),HTMLMediaElement.prototype.nativePlay=HTMLMediaElement.prototype.play,HTMLMediaElement.prototype.play=function(){var myself=this,promise=myself.nativePlay();promise&&promise.then(function(){setTimeout(function(){myself.nativePlay().then(function(){})["catch"](function(){alert("Video requires manual action to start the player.")})},1e3)})["catch"](function(){setTimeout(function(){myself.nativePlay().then(function(){})["catch"](function(){alert("Video requires manual action to start the player.")})},1e3)})},getUserMedia=window.navigator&&window.navigator.getUserMedia),"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia&&window.mozRTCPeerConnection){if(webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,getUserMedia=function(constraints,onSuccess,onError){var constraintsToFF37=function(c){if("object"!=typeof c||c.require)return c;var require=[];return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r=c[key]="object"==typeof c[key]?c[key]:{ideal:c[key]};if((void 0!==r.min||void 0!==r.max||void 0!==r.exact)&&require.push(key),void 0!==r.exact&&("number"==typeof r.exact?r.min=r.max=r.exact:c[key]=r.exact,delete r.exact),void 0!==r.ideal){c.advanced=c.advanced||[];var oc={};"number"==typeof r.ideal?oc[key]={min:r.ideal,max:r.ideal}:oc[key]=r.ideal,c.advanced.push(oc),delete r.ideal,Object.keys(r).length||delete c[key]}}}),require.length&&(c.require=require),c};return 38>webrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,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)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38;var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")})}else navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)?(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=12):webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcDetectedBrowser="not a browser";"undefined"!=typeof module?module.exports={getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}:"function"==typeof require&&"function"==typeof define&&define([],function(){return{getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}}),"undefined"!=typeof webrtcUtils&&(webrtcUtils.enableLogs=!1);var currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[],remove:function(idInstance){this.mutex=!1;var stream=this.streams[idInstance];if(stream){stream=stream.stream;var options=stream.currentUserMediaRequestOptions;this.queueRequests.indexOf(options)&&(delete this.queueRequests[this.queueRequests.indexOf(options)],this.queueRequests=removeNullEntries(this.queueRequests)),this.streams[idInstance].stream=null,delete this.streams[idInstance]}}},StreamsHandler=function(){function handleType(type){return type?"string"==typeof type||"undefined"==typeof type?type:type.audio&&type.video?null:type.audio?"audio":type.video?"video":void 0:void 0}function setHandlers(stream,syncAction,connection){function graduallyIncreaseVolume(){if(connection.streamEvents[stream.streamid].mediaElement){var mediaElement=connection.streamEvents[stream.streamid].mediaElement;mediaElement.volume=0,afterEach(200,5,function(){mediaElement.volume+=.2})}}stream&&stream.addEventListener&&(("undefined"==typeof syncAction||1==syncAction)&&stream.addEventListener("ended",function(){StreamsHandler.onSyncNeeded(this.streamid,"ended")},!1),stream.mute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!1,connection.streamEvents[stream.streamid].isAudioMuted=!0}),("undefined"==typeof type||"video"==type)&&stream.getVideoTracks().forEach(function(track){track.enabled=!1}),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"mute",type),connection.streamEvents[stream.streamid].muteType=type||"both",fireEvent(stream,"mute",type)},stream.unmute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),graduallyIncreaseVolume(),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!0,connection.streamEvents[stream.streamid].isAudioMuted=!1}),("undefined"==typeof type||"video"==type)&&(stream.getVideoTracks().forEach(function(track){track.enabled=!0}),"undefined"!=typeof type&&"video"==type&&connection.streamEvents[stream.streamid].isAudioMuted&&!function looper(times){times||(times=0),times++,100>times&&connection.streamEvents[stream.streamid].isAudioMuted&&(stream.mute("audio"),setTimeout(function(){looper(times)},50))}()),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"unmute",type),connection.streamEvents[stream.streamid].unmuteType=type||"both",fireEvent(stream,"unmute",type)})}function afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes){startedTimes=(startedTimes||0)+1,startedTimes>=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();!function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}(),function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}if(-1!==document.domain.indexOf("webrtc-experiment.com")){window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}}();var TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(-1!==progress.position){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(!chunk.remoteUserId||(helper=progressHelper[chunk.uuid][chunk.remoteUserId]))&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exists.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;-1!=file.type.indexOf("image")?div.innerHTML='Download '+file.name+'
':div.innerHTML='Download '+file.name+'
'}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"===response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}}}return{handle:handle}}();window.RTCMultiConnection=RTCMultiConnection}(); \ No newline at end of file diff --git a/RTCMultiConnection/Scalable-Broadcast.js b/RTCMultiConnection/Scalable-Broadcast.js index 211a41d4..f78c6eae 100755 --- a/RTCMultiConnection/Scalable-Broadcast.js +++ b/RTCMultiConnection/Scalable-Broadcast.js @@ -2,91 +2,217 @@ // MIT License - www.WebRTC-Experiment.com/licence // Documentation - github.com/muaz-khan/RTCMultiConnection -var listOfBroadcasts = {}; -var listOfSockets = {}; +var users = {}; -module.exports = exports = function(socket, singleBroadcastAttendees) { +module.exports = exports = function(socket, maxRelayLimitPerUser) { try { - singleBroadcastAttendees = parseInt(singleBroadcastAttendees) || 3; + maxRelayLimitPerUser = parseInt(maxRelayLimitPerUser) || 2; } catch (e) { - singleBroadcastAttendees = 3; + maxRelayLimitPerUser = 2; } - var currentUser; socket.on('join-broadcast', function(user) { try { - currentUser = user; - - user.numberOfViewers = 0; - if (!listOfBroadcasts[user.broadcastid]) { - listOfBroadcasts[user.broadcastid] = { - numberOfViewers: 0, - broadcasters: {}, - allusers: {}, - typeOfStreams: user.typeOfStreams // object-booleans: audio, video, screen - }; + if(!users[user.userid]) { + socket.userid = user.userid; + socket.isScalableBroadcastSocket = true; + + users[user.userid] = { + userid: user.userid, + broadcastId: user.broadcastId, + isBroadcastInitiator: false, + maxRelayLimitPerUser: maxRelayLimitPerUser, + relayReceivers: [], + receivingFrom: null, + canRelay: false, + typeOfStreams: user.typeOfStreams || {audio: true, video: true}, + socket: socket + } } - if(!listOfSockets[user.userid]) { - listOfSockets[user.userid] = socket; + var relayUser = getFirstAvailableBraodcater(user.broadcastId, maxRelayLimitPerUser); + + if(relayUser === 'ask-him-rejoin') { + socket.emit('rejoin-broadcast', user.broadcastId); + return; } - var firstAvailableBroadcaster = getFirstAvailableBraodcater(user, singleBroadcastAttendees); - if (firstAvailableBroadcaster) { - listOfBroadcasts[user.broadcastid].broadcasters[firstAvailableBroadcaster.userid].numberOfViewers++; - socket.emit('join-broadcaster', firstAvailableBroadcaster, listOfBroadcasts[user.broadcastid].typeOfStreams); + if (relayUser && user.userid !== user.broadcastId) { + var hintsToJoinBroadcast = { + typeOfStreams: relayUser.typeOfStreams, + userid: relayUser.userid + }; - socket.emit('logs', 'You <' + user.userid + '> are getting data/stream from <' + firstAvailableBroadcaster.userid + '>'); + users[user.userid].receivingFrom = relayUser.userid; + users[relayUser.userid].relayReceivers.push( + users[user.userid] + ); + users[user.broadcastId].lastRelayuserid = relayUser.userid; - var remoteSocket = listOfSockets[firstAvailableBroadcaster.userid]; - remoteSocket.emit('logs', 'You <' + firstAvailableBroadcaster.userid + '>' + ' are now relaying/forwarding data/stream to <' + user.userid + '>'); + socket.emit('join-broadcaster', hintsToJoinBroadcast); + + // logs for current socket + socket.emit('logs', 'You <' + user.userid + '> are getting data/stream from <' + relayUser.userid + '>'); + + // logs for target relaying user + relayUser.socket.emit('logs', 'You <' + relayUser.userid + '>' + ' are now relaying/forwarding data/stream to <' + user.userid + '>'); } else { - currentUser.isInitiator = true; - socket.emit('start-broadcasting', listOfBroadcasts[user.broadcastid].typeOfStreams); + users[user.userid].isBroadcastInitiator = true; + socket.emit('start-broadcasting', users[user.userid].typeOfStreams); + // logs to tell he is now broadcast initiator socket.emit('logs', 'You <' + user.userid + '> are now serving the broadcast.'); } + } catch (e) { + consoleLog(e); + } + }); + + socket.on('scalable-broadcast-message', function(message) { + socket.broadcast.emit('scalable-broadcast-message', message); + }); - listOfBroadcasts[user.broadcastid].broadcasters[user.userid] = user; - listOfBroadcasts[user.broadcastid].allusers[user.userid] = user; - } catch (e) {} + socket.on('can-relay-broadcast', function() { + if(users[socket.userid]) { + users[socket.userid].canRelay = true; + } }); - socket.on('message', function(message) { - socket.broadcast.emit('message', message); + socket.on('can-not-relay-broadcast', function() { + if(users[socket.userid]) { + users[socket.userid].canRelay = false; + } + }); + + socket.on('check-broadcast-presence', function(userid, callback) { + // we can pass number of viewers as well + try { + callback(!!users[userid] && users[userid].isBroadcastInitiator === true); + } + catch(e) { + consoleLog(e); + } }); socket.on('disconnect', function() { try { - if (!currentUser) return; - - if(listOfSockets[currentUser.userid]) { - delete listOfSockets[currentUser.userid]; + if (!socket.isScalableBroadcastSocket) return; + + var user = users[socket.userid]; + + if(!user) return; + + if(user.isBroadcastInitiator === true) { + consoleLog({ + 'initiator-left': true, + 'userid': user.userid, + 'broadcastId': user.broadcastId, + 'isBroadcastInitiator': user.isBroadcastInitiator, + 'relayReceivers': Object.keys(user.relayReceivers) + }); + + // need to stop entire broadcast? + for(var n in users) { + var _user = users[n]; + + if(_user.broadcastId === user.broadcastId) { + _user.socket.emit('broadcast-stopped', user.broadcastId); + } + } + + delete users[socket.userid]; + return; } - if (!listOfBroadcasts[currentUser.broadcastid]) return; - if (!listOfBroadcasts[currentUser.broadcastid].broadcasters[currentUser.userid]) return; + if(user.receivingFrom || user.isBroadcastInitiator === true) { + var parentUser = users[user.receivingFrom]; + + if(parentUser) { + var newArray = []; + parentUser.relayReceivers.forEach(function(n) { + if(n.userid !== user.userid) { + newArray.push(n); + } + }); + users[user.receivingFrom].relayReceivers = newArray; + } + } - delete listOfBroadcasts[currentUser.broadcastid].broadcasters[currentUser.userid]; - if (currentUser.isInitiator) { - delete listOfBroadcasts[currentUser.broadcastid]; + if(user.relayReceivers.length && user.isBroadcastInitiator === false) { + askNestedUsersToRejoin(user.relayReceivers); } - } catch (e) {} + + delete users[socket.userid]; + } catch (e) { + consoleLog(e); + } }); }; -function getFirstAvailableBraodcater(user, singleBroadcastAttendees) { +function askNestedUsersToRejoin(relayReceivers) { try { - var broadcasters = listOfBroadcasts[user.broadcastid].broadcasters; - var firstResult; - for (var userid in broadcasters) { - if (!firstResult && broadcasters[userid].numberOfViewers <= singleBroadcastAttendees) { - firstResult = broadcasters[userid]; + var usersToAskRejoin = []; + + relayReceivers.forEach(function(receiver) { + if(!!users[receiver.userid]) { + users[receiver.userid].canRelay = false; + users[receiver.userid].receivingFrom = null; + receiver.socket.emit('rejoin-broadcast', receiver.broadcastId); + } + + }); + } + catch(e) { + consoleLog(e); + } +} + +function getFirstAvailableBraodcater(broadcastId, maxRelayLimitPerUser) { + try { + var broadcastInitiator = users[broadcastId]; + + // if initiator is capable to receive users + if(broadcastInitiator.relayReceivers.length < maxRelayLimitPerUser) { + return broadcastInitiator; + } + + // otherwise if initiator knows who is current relaying user + if(broadcastInitiator.lastRelayuserid) { + var lastRelayUser = users[broadcastInitiator.lastRelayuserid]; + if(lastRelayUser && lastRelayUser.relayReceivers.length < maxRelayLimitPerUser) { + return lastRelayUser; + } + } + + // otherwise, search for a user who not relayed anything yet + var userFound; + for(var n in users) { + var user = users[n]; + + if(userFound) { continue; - } else { - delete listOfBroadcasts[user.broadcastid].broadcasters[userid]; } + else if(user.broadcastId === broadcastId) { + if(!user.relayReceivers.length && user.canRelay === true) { + userFound = user; + } + } + } + + if(userFound) { + return userFound; } - return firstResult; - } catch (e) {} + + // need to increase "maxRelayLimitPerUser" in this situation + // so that each relaying user can distribute the bandwidth + return broadcastInitiator; + } catch (e) { + consoleLog(e); + } } + +function consoleLog() { + // return; // comment this line for development testings + + console.log(arguments); +} \ No newline at end of file diff --git a/RTCMultiConnection/Signaling-Server.js b/RTCMultiConnection/Signaling-Server.js index 2eeffd25..3602081d 100755 --- a/RTCMultiConnection/Signaling-Server.js +++ b/RTCMultiConnection/Signaling-Server.js @@ -3,22 +3,31 @@ // Documentation - github.com/muaz-khan/RTCMultiConnection module.exports = exports = function(app, socketCallback) { - var io = require('socket.io').listen(app, { - log: false, - origins: '*:*' - }); - - io.set('transports', [ - 'websocket', // 'disconnect' EVENT will work only with 'websocket' - 'xhr-polling', - 'jsonp-polling' - ]); - var listOfUsers = {}; var shiftedModerationControls = {}; var ScalableBroadcast; - io.sockets.on('connection', function(socket) { + var io = require('socket.io'); + + try { + io = io(app); + io.on('connection', onConnection); + } catch (e) { + io = io.listen(app, { + log: false, + origins: '*:*' + }); + + io.set('transports', [ + 'websocket', // 'disconnect' EVENT will work only with 'websocket' + 'xhr-polling', + 'jsonp-polling' + ]); + + io.sockets.on('connection', onConnection); + } + + function onConnection(socket) { var params = socket.handshake.query; var socketMessageEvent = params.msgEvent || 'RTCMultiConnection-Message'; @@ -26,12 +35,19 @@ module.exports = exports = function(app, socketCallback) { if (!ScalableBroadcast) { ScalableBroadcast = require('./Scalable-Broadcast.js'); } - var singleBroadcastAttendees = params.singleBroadcastAttendees; - ScalableBroadcast(socket, singleBroadcastAttendees); + ScalableBroadcast(socket, params.maxRelayLimitPerUser); } - socket.userid = params.userid; + // temporarily disabled + if (false && !!listOfUsers[params.userid]) { + params.dontUpdateUserId = true; + var useridAlreadyTaken = params.userid; + params.userid = (Math.random() * 1000).toString().replace('.', ''); + socket.emit('userid-already-taken', useridAlreadyTaken, params.userid); + } + + socket.userid = params.userid; listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, @@ -57,6 +73,13 @@ module.exports = exports = function(app, socketCallback) { } catch (e) {} }); + socket.on('dont-make-me-moderator', function() { + try { + if (!listOfUsers[socket.userid]) return; + listOfUsers[socket.userid].isPublic = false; + } catch (e) {} + }); + socket.on('get-public-moderators', function(userIdStartsWith, callback) { try { userIdStartsWith = userIdStartsWith || ''; @@ -75,7 +98,12 @@ module.exports = exports = function(app, socketCallback) { } catch (e) {} }); - socket.on('changed-uuid', function(newUserId) { + socket.on('changed-uuid', function(newUserId, callback) { + if (params.dontUpdateUserId) { + delete params.dontUpdateUserId; + return; + } + try { if (listOfUsers[socket.userid] && listOfUsers[socket.userid].socket.id == socket.userid) { if (newUserId === socket.userid) return; @@ -84,6 +112,8 @@ module.exports = exports = function(app, socketCallback) { listOfUsers[newUserId] = listOfUsers[oldUserId]; listOfUsers[newUserId].socket.userid = socket.userid = newUserId; delete listOfUsers[oldUserId]; + + callback(); return; } @@ -94,6 +124,8 @@ module.exports = exports = function(app, socketCallback) { isPublic: false, extra: {} }; + + callback(); } catch (e) {} }); @@ -122,6 +154,24 @@ module.exports = exports = function(app, socketCallback) { } catch (e) {} }); + socket.on('close-entire-session', function(callback) { + try { + var connectedWith = listOfUsers[socket.userid].connectedWith; + Object.keys(connectedWith).forEach(function(key) { + if (connectedWith[key] && connectedWith[key].emit) { + try { + connectedWith[key].emit('closed-entire-session', socket.userid, listOfUsers[socket.userid].extra); + } catch (e) {} + } + }); + + delete shiftedModerationControls[socket.userid]; + callback(); + } catch (e) { + throw e; + } + }); + function onMessageCallback(message) { try { if (!listOfUsers[message.sender]) { @@ -129,7 +179,7 @@ module.exports = exports = function(app, socketCallback) { return; } - if (!listOfUsers[message.sender].connectedWith[message.remoteUserId] && !!listOfUsers[message.remoteUserId]) { + if (!message.message.userLeft && !listOfUsers[message.sender].connectedWith[message.remoteUserId] && !!listOfUsers[message.remoteUserId]) { listOfUsers[message.sender].connectedWith[message.remoteUserId] = listOfUsers[message.remoteUserId].socket; listOfUsers[message.sender].socket.emit('user-connected', message.remoteUserId); @@ -245,6 +295,13 @@ module.exports = exports = function(app, socketCallback) { try { var message = shiftedModerationControls[socket.userid]; + if (message) { + delete shiftedModerationControls[message.userid]; + onMessageCallback(message); + } + } catch (e) {} + + try { // inform all connected users if (listOfUsers[socket.userid]) { for (var s in listOfUsers[socket.userid].connectedWith) { @@ -256,18 +313,13 @@ module.exports = exports = function(app, socketCallback) { } } } - - if (message) { - onMessageCallback(message); - delete shiftedModerationControls[message.userid]; - } - - delete listOfUsers[socket.userid]; } catch (e) {} + + delete listOfUsers[socket.userid]; }); if (socketCallback) { socketCallback(socket); } - }); + } }; diff --git a/RTCMultiConnection/demos/Audio+ScreenSharing.html b/RTCMultiConnection/demos/Audio+ScreenSharing.html index ecbcb328..4601511a 100755 --- a/RTCMultiConnection/demos/Audio+ScreenSharing.html +++ b/RTCMultiConnection/demos/Audio+ScreenSharing.html @@ -65,7 +65,8 @@

Audio + ScreenSharing using RTCMultiConnection-v3.0

- + + @@ -96,7 +97,24 @@

Audio + ScreenSharing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'audio-plus-screen-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: 'two-way', @@ -108,9 +126,13 @@

Audio + ScreenSharing using RTCMultiConnection-v3.0

OfferToReceiveVideo: false }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; diff --git a/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html b/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html index ac265929..ecddde87 100755 --- a/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html +++ b/RTCMultiConnection/demos/Audio+Video+TextChat+FileSharing.html @@ -64,6 +64,8 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0



+

+
@@ -74,8 +76,11 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0

- - + + + + + @@ -100,6 +105,21 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0

connection.openOrJoin(document.getElementById('room-id').value); }; + document.getElementById('btn-leave-room').onclick = function() { + this.disabled = true; + + if(connection.isInitiator) { + // use this method if you did NOT set "autoCloseEntireSession===true" + // for more info: https://github.com/muaz-khan/RTCMultiConnection#closeentiresession + connection.closeEntireSession(function() { + document.querySelector('h1').innerHTML = 'Entire session has been closed.'; + }); + } + else { + connection.leave(); + } + }; + // ...................................................... // ................FileSharing/TextChat Code............. // ...................................................... @@ -141,7 +161,25 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'audio-video-file-chat-demo'; + + var roomid = ''; + if(localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } + else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.enableFileSharing = true; // by default, it is "false". @@ -156,9 +194,13 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; connection.onmessage = appendDIV; @@ -167,6 +209,42 @@

Audio+Video+TextChat+FileSharing using RTCMultiConnection-v3.0

connection.onopen = function() { document.getElementById('share-file').disabled = false; document.getElementById('input-text-chat').disabled = false; + document.getElementById('btn-leave-room').disabled = false; + + document.querySelector('h1').innerHTML = 'You are connected with: ' + connection.getAllParticipants().join(', '); + }; + + connection.onclose = function() { + if(connection.getAllParticipants().length) { + document.querySelector('h1').innerHTML = 'You are still connected with: ' + connection.getAllParticipants().join(', '); + } + else { + document.querySelector('h1').innerHTML = 'Seems session has been closed or all participants left.'; + } + }; + + connection.onEntireSessionClosed = function(event) { + document.getElementById('share-file').disabled = true; + document.getElementById('input-text-chat').disabled = true; + document.getElementById('btn-leave-room').disabled = true; + + document.getElementById('open-or-join-room').disabled = false; + document.getElementById('open-room').disabled = false; + document.getElementById('join-room').disabled = false; + document.getElementById('room-id').disabled = false; + + connection.attachStreams.forEach(function(stream) { + stream.stop(); + }); + + // don't display alert for moderator + if(connection.userid === event.userid) return; + document.querySelector('h1').innerHTML = 'Entire session has been closed by the moderator: ' + event.userid; + }; + + connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { + // seems room is already opened + connection.join(useridAlreadyTaken); }; diff --git a/RTCMultiConnection/demos/Audio-Conferencing.html b/RTCMultiConnection/demos/Audio-Conferencing.html index ffe11fee..a37301de 100755 --- a/RTCMultiConnection/demos/Audio-Conferencing.html +++ b/RTCMultiConnection/demos/Audio-Conferencing.html @@ -65,7 +65,8 @@

Audio Conferencing using RTCMultiConnection-v3.0

- + + @@ -96,7 +97,24 @@

Audio Conferencing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'audio-conference-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true @@ -107,9 +125,13 @@

Audio Conferencing using RTCMultiConnection-v3.0

OfferToReceiveVideo: false }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; diff --git a/RTCMultiConnection/demos/Disconnect+Rejoin.html b/RTCMultiConnection/demos/Disconnect+Rejoin.html index a76d516d..7cecb1b5 100755 --- a/RTCMultiConnection/demos/Disconnect+Rejoin.html +++ b/RTCMultiConnection/demos/Disconnect+Rejoin.html @@ -78,8 +78,11 @@

Disconnect+Rejoin using RTCMultiConnection-v3.0

- - + + + + + @@ -157,10 +160,26 @@

Disconnect+Rejoin using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'disconnect-rejoin-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.enableFileSharing = true; // by default, it is "false". - connection.autoReDialOnFailure = false; // to support manual rejoin connection.session = { audio: true, @@ -173,9 +192,13 @@

Disconnect+Rejoin using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; connection.onmessage = appendDIV; diff --git a/RTCMultiConnection/demos/Files-Scalable-Broadcast.html b/RTCMultiConnection/demos/Files-Scalable-Broadcast.html index 99dcc64e..c27fb11a 100755 --- a/RTCMultiConnection/demos/Files-Scalable-Broadcast.html +++ b/RTCMultiConnection/demos/Files-Scalable-Broadcast.html @@ -106,16 +106,29 @@

WebRTC Scal This module simply initializes socket.io and configures it in a way that single file can be shared/relayed over unlimited users without any bandwidth/CPU usage issues. Everything happens peer-to-peer!

- Share camera with unlimited users using p2p methods! Video Scalable Broadcast + Share camera with unlimited users using p2p methods! Video Scalable Broadcast or all-in-one scalable broadcast. - - + + + + + + - + + + + + + + + + + MultiRTC! / RTCMultiConnection Demo ® Muaz Khan + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +

+
+ MultiRTC! +

+ +
+
0
+ +
+ +
+ + +

+ +
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Set Bandwidth


+ + kbps +
+ + kbps +
+

Set Resolutions


+ + +
+ + +
+ +
+ + + chars
+ + milliseconds

+ + + +
+

Select Devices


+ + +
+ + +
+ +

+ + + +
+

Select Candidates


+ +
+ + +
+ + + +
+

Set DataChannel Options


+ + +
+

Set SDP Constraints


+ +
+ + +
+ + + +
+ +
+ +
+ +
+
+ + +
+
+ MultiRTC is open-sourced here! +
+ +

Welcome Sir! (Dev-Notes: Still/known bugs in this v3.0 demo)

+

+ Please Enter a UNIQUE room-name:
+ You can enter email; or any string or numbers!
+ You MUST manually share this unique room-name with target users so they can join you!

+ + Please Enter Your Full Name: +

+
+
+ +
+ + +
+

What is MultiRTC?

+

+

    +
  1. + It is a skype-like demo using WebRTC for realtime connections! +
  2. + +
  3. + It allows you enable/disable webcams; and join with or without webcams! +
  4. + +
  5. + It allows you share screen using existing peer connections! +
  6. + +
  7. + It allows you share files with preview and download links! +
  8. + +
  9. + It allows you auto translate incoming messages in your own language! +
  10. + +
  11. + It gives you full control over bandwidth and screen resolutions! +
  12. + +
  13. + It allows you adjust file sharing speed yourself by setting chunk-size and chunk-intervals! +
  14. + +
  15. + It allows you test all WebRTC features by enabling/disabling some check-boxes! +
  16. +
+

+
+
+
+ + + + + + + + + + + diff --git a/RTCMultiConnection/demos/MultiRTC/linkify.js b/RTCMultiConnection/demos/MultiRTC/linkify.js new file mode 100755 index 00000000..3119d612 --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/linkify.js @@ -0,0 +1,156 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection + +window.linkify = (function(){ + var + SCHEME = "[a-z\\d.-]+://", + IPV4 = "(?:(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(?:[0-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])", + HOSTNAME = "(?:(?:[^\\s!@#$%^&*()_=+[\\]{}\\\\|;:'\",.<>/?]+)\\.)+", + TLD = "(?:ac|ad|aero|ae|af|ag|ai|al|am|an|ao|aq|arpa|ar|asia|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|biz|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|cat|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|coop|com|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|info|int|in|io|iq|ir|is|it|je|jm|jobs|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mobi|mo|mp|mq|mr|ms|mt|museum|mu|mv|mw|mx|my|mz|name|na|nc|net|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pro|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm|tn|to|tp|travel|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)", + HOST_OR_IP = "(?:" + HOSTNAME + TLD + "|" + IPV4 + ")", + PATH = "(?:[;/][^#?<>\\s]*)?", + QUERY_FRAG = "(?:\\?[^#<>\\s]*)?(?:#[^<>\\s]*)?", + URI1 = "\\b" + SCHEME + "[^<>\\s]+", + URI2 = "\\b" + HOST_OR_IP + PATH + QUERY_FRAG + "(?!\\w)", + + MAILTO = "mailto:", + EMAIL = "(?:" + MAILTO + ")?[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@" + HOST_OR_IP + QUERY_FRAG + "(?!\\w)", + + URI_RE = new RegExp( "(?:" + URI1 + "|" + URI2 + "|" + EMAIL + ")", "ig" ), + SCHEME_RE = new RegExp( "^" + SCHEME, "i" ), + + quotes = { + "'": "`", + '>': '<', + ')': '(', + ']': '[', + '}': '{', + '»': '«', + '›': '‹' + }, + + default_options = { + callback: function( text, href ) { + return href ? '' + text + '<\/a>' : text; + }, + punct_regexp: /(?:[!?.,:;'"]|(?:&|&)(?:lt|gt|quot|apos|raquo|laquo|rsaquo|lsaquo);)$/ + }; + + return function( txt, options ) { + txt = getSmileys(txt); + txt = txt.replace(//g, '>').replace(/ /g, ' ').replace(/\n/g, '
'); + + options = options || {}; + + // Temp variables. + var arr, + i, + link, + href, + + // Output HTML. + html = '', + + // Store text / link parts, in order, for re-combination. + parts = [], + + // Used for keeping track of indices in the text. + idx_prev, + idx_last, + idx, + link_last, + + // Used for trimming trailing punctuation and quotes from links. + matches_begin, + matches_end, + quote_begin, + quote_end; + + // Initialize options. + for ( i in default_options ) { + if ( options[ i ] === undefined ) { + options[ i ] = default_options[ i ]; + } + } + + // Find links. + while ( arr = URI_RE.exec( txt ) ) { + + link = arr[0]; + idx_last = URI_RE.lastIndex; + idx = idx_last - link.length; + + // Not a link if preceded by certain characters. + if ( /[\/:]/.test( txt.charAt( idx - 1 ) ) ) { + continue; + } + + // Trim trailing punctuation. + do { + // If no changes are made, we don't want to loop forever! + link_last = link; + + quote_end = link.substr( -1 ) + quote_begin = quotes[ quote_end ]; + + // Ending quote character? + if ( quote_begin ) { + matches_begin = link.match( new RegExp( '\\' + quote_begin + '(?!$)', 'g' ) ); + matches_end = link.match( new RegExp( '\\' + quote_end, 'g' ) ); + + // If quotes are unbalanced, remove trailing quote character. + if ( ( matches_begin ? matches_begin.length : 0 ) < ( matches_end ? matches_end.length : 0 ) ) { + link = link.substr( 0, link.length - 1 ); + idx_last--; + } + } + + // Ending non-quote punctuation character? + if ( options.punct_regexp ) { + link = link.replace( options.punct_regexp, function(a){ + idx_last -= a.length; + return ''; + }); + } + } while ( link.length && link !== link_last ); + + href = link; + + // Add appropriate protocol to naked links. + if ( !SCHEME_RE.test( href ) ) { + href = ( href.indexOf( '@' ) !== -1 ? ( !href.indexOf( MAILTO ) ? '' : MAILTO ) + : !href.indexOf( 'irc.' ) ? 'irc://' + : !href.indexOf( 'ftp.' ) ? 'ftp://' + : 'http://' ) + + href; + } + + // Push preceding non-link text onto the array. + if ( idx_prev != idx ) { + parts.push([ txt.slice( idx_prev, idx ) ]); + idx_prev = idx_last; + } + + // Push massaged link onto the array + parts.push([ link, href ]); + }; + + // Push remaining non-link text onto the array. + parts.push([ txt.substr( idx_prev ) ]); + + // Process the array items. + for ( i = 0; i < parts.length; i++ ) { + html += options.callback.apply( window, parts[i] ); + } + + // In case of catastrophic failure, return the original text; + return html || txt; + }; + +})(); + +function getSmileys(text) { + return text; + // return text.replace(/:\)/g, ''); +} diff --git a/RTCMultiConnection/demos/MultiRTC/scrol-bars.css b/RTCMultiConnection/demos/MultiRTC/scrol-bars.css new file mode 100755 index 00000000..ec641447 --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/scrol-bars.css @@ -0,0 +1,82 @@ +/* +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection +*/ + +textarea,input { + font-size: 1em; +} + +script { + height: 0; + width: 0; + margin: 0; + padding: 0; +} + +a,a:visited,a.cdchrm:visited { + text-decoration: none; + color: #0044cc; +} + +a:hover,a.cdchrm:hover { + text-decoration: underline; +} + +img { + border-width: 0; +} + +hr { + color: #ccc; + background-color: #ccc; + height: 1px; + border-bottom-width: 0px; + border-top-width: 0px; + margin-bottom: 7px; +} + +* { + scrollbar-base-color: white; + scrollbar-track-color: white; + scrollbar-darkshadow-color: white; + scrollbar-3dlight-color: white; + scrollbar-arrow-color: #757778; + scrollbar-shadow-color: #bec1c4; + scrollbar-highlight-color: #bec1c4; +} + +::-webkit-scrollbar { + width: 15px; + height: 15px; +} + +::-webkit-scrollbar-track-piece { + background-color: white; +} + +::-webkit-scrollbar-button:start:decrement,::-webkit-scrollbar-button:end:increment,::-webkit-scrollbar-thumb { + background-color: white; + border: solid 1px #bec1c4; +} + +::-webkit-scrollbar-button:vertical:start:decrement { + background-image: url('images/scrollbar_arrow_up.gif'); + background-position: center; +} + +::-webkit-scrollbar-button:vertical:end:increment { + background-image: url('images/scrollbar_arrow_down.gif'); + background-position: center; +} + +::-webkit-scrollbar-button:horizontal:start:decrement { + background-image: url('images/scrollbar_arrow_left.gif'); + background-position: center; +} + +::-webkit-scrollbar-button:horizontal:end:increment { + background-image: url('images/scrollbar_arrow_right.gif'); + background-position: center; +} diff --git a/RTCMultiConnection/demos/MultiRTC/style.css b/RTCMultiConnection/demos/MultiRTC/style.css new file mode 100755 index 00000000..3ab4ecb3 --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/style.css @@ -0,0 +1,345 @@ +/* +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection +*/ + +body, body * { + padding: 0; + margin: 0; + outline: none; + -webkit-user-drag: none; + font-family: 'Open Sans', sans-serif; + word-wrap: break-word; +} + +html { + background: rgb(247, 253, 244); +} + +body { + border-right: 2px solid rgb(23, 228, 56); + border-left: 2px solid rgb(80, 238, 29); + margin: 0 2%; + background: white; + font-size: 1.2em; + line-height: 1.5em; +} + +body { + padding: 30px 30px; +} + +.main { + border: 1px solid black; + border-radius: 6px; +} + +.new-message { + min-height: 7em; + border-top: 1px solid black; +} + +.new-message:first-child { + border-top: 0; +} + +.user-info { + width: 10em; + min-height: 7em; + float: left; + border-right: 1px solid black; + text-align: center; +} + +.user-info img, .user-info video { + width: 100%; + height: 134px; +} + +.user-info audio { + width: 100%; +} + +.user-activity { + margin-left: 10em; + padding: .4em .8em; +} + +.header { +} + +.message { + padding: .6em .8em; + word-wrap: break-word; +} + +.message img, .message iframe { + width: 100%; + border: 2px solid black; + border-radius: 5px; +} + +.main-input-box { + border: 1px solid black; + border-bottom: 0; + padding-bottom: 1px; + text-align: right; + margin-left: 193px; + margin-right: 125px; + padding-right: 21px; +} + +.main-input-box textarea { + border: 0; + width: 100%; + resize: none; + padding: .4em .8em; + font-size: inherit; + font-family: inherit; + padding-right: 177px; + background: transparent; +} + +button, .users-list +{ + padding: 5px 10px; + cursor: pointer; + display:inline-block; + color: #FDFDFD; + font-size: 20px; + color: black; + border: 1px solid rgba(231, 72, 50, 0.59); + border-radius: 2px; + background-color: rgba(204, 60, 25, 0.63); +} +button:hover, .users-list:hover +{ + background-color: rgba(204, 60, 25, 0.83); +} +button:active, .users-list:active +{ + background-color: rgba(204, 60, 25, 0.9); +} + +button[disabled], input[type=button][disabled] { + background-color: transparent; + border: 1px solid rgb(187, 181, 181); + color: gray; + text-shadow: none; +} + +.icon { + width: 50px; + height: 50px; + background-repeat: no-repeat; + background-position: center center; +} + +#allow-webcam { + background-image: url('images/webcam.png'); +} + +#allow-mic { + background-image: url('images/microphone.png'); +} + +#allow-screen { + background-image: url('images/screen.png'); +} + +#share-files { + background-image: url('images/share-files.png'); +} + +#continue { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + background-repeat: no-repeat; + background-position: center right; + background-image: url('images/action-needed.png'); + padding-right: 47px; + padding-top: 1px; + padding-bottom: 4px; +} + +a { + color: #2844FA; + text-decoration: none; +} + +a:hover, a:focus { + color: #1B29A4; +} + +a:active { + color: #000; +} + +input { + border-radius: 1px; + border: 1px solid #d9d9d9; + font-size: 15px; + height: 32px; + width: 200px; + margin-top: .5em; + padding: 0 8px; + -moz-user-select: initial; + -o-user-select: initial; + -webkit-user-select: initial; + -ms-user-select: initial; + user-select: initial; +} + +input[type=checkbox] { + width: 30px; + height: 30px; + margin: 0; + vertical-align: middle; +} + +h1 { + position: absolute; + color: rgb(10, 175, 253); + text-shadow: 1px 1px rgb(7, 129, 160); +} + +h1 img { + width: 160px; + height: 60px; +} + +.users-list { + position: absolute; + right: 63px; + padding: 0; + color: white; + margin-top: 0px; + width: 118px; + height: 126px; + cursor: default; + border-radius: 0; + border-top-right-radius: 20px; +} + +.users-list.selected { + background-color: black; +} + +.users-list img { + width: 82%; + height: 104px; +} + +.numbers-of-users { + position: absolute; + font-size: 3em; + left: 38%; + top: 60px; +} + +.users-container, .settings-panel { + margin: 0 0 13px 0; + border: 1px solid black; + overflow: hidden; + overflow-x: auto; + height: 180px; + border-radius: 5px; + display: none; +} + +.users-container video { + height: 100%; +} + +.self-url { + position: absolute; + top: 5px; + left: 251px; + height: 25px; + overflow: hidden; +} + +.settings { + position: absolute; + top: 33px; + right: 210px; +} + +#settings { + background-repeat: no-repeat; + background-position: center right; + background-image: url('images/settings.png'); + padding-right: 35px; + margin-top: -1px; +} + +#save-settings { + background-repeat: no-repeat; + background-position: center right; + background-image: url('images/accepted.png'); + padding-right: 53px; + border-color: rgb(133, 21, 21); +} + +.settings-panel { + height: auto; + overflow: hidden; + padding: .4em .8em; +} + +strong { + color: rgb(204, 14, 14); + font-family: inherit; + font-weight: normal; +} + +tr, td, th { + vertical-align: top; + padding: .7em 1.4em; + border-top: 1px dotted #BBA9A9; + border-right: 1px dotted #BBA9A9; +} + +table { + width: 100%; +} + +h1, h2 { + border-bottom: 1px solid rgb(189, 189, 189); + display: inline; + font-weight: normal; + line-height: 36px; + padding: 0 0 3px 0; +} + +select { + border: 1px solid #d9d9d9; + border-radius: 1px; + padding: .4em .4em; + margin-top: 5px; +} + +label.adjust-width { + display: inline-block; + width: 30%; +} + +small { + display: inline-block; + vertical-align: middle; +} + +.smiley { + width: 20px!important; + height: 20px; + border: 0!important; + vertical-align: middle; +} +#who-is-typing +{ + text-align: left; + position: absolute; + margin: 0 18px; + color: red; +} diff --git a/RTCMultiConnection/demos/MultiRTC/ui.main.js b/RTCMultiConnection/demos/MultiRTC/ui.main.js new file mode 100755 index 00000000..8ba5efdd --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/ui.main.js @@ -0,0 +1,255 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection + +function getElement(selector) { + return document.querySelector(selector); +} + +var main = getElement('.main'); + +function getRandomColor() { + var letters = '0123456789ABCDEF'.split(''); + var color = '#'; + for (var i = 0; i < 6; i++) { + color += letters[Math.round(Math.random() * 15)]; + } + return color; +} + +function addNewMessage(args) { + var newMessageDIV = document.createElement('div'); + newMessageDIV.className = 'new-message'; + + var userinfoDIV = document.createElement('div'); + userinfoDIV.className = 'user-info'; + userinfoDIV.innerHTML = args.userinfo || ''; + + userinfoDIV.style.background = args.color || rtcMultiConnection.extra.color || getRandomColor(); + + newMessageDIV.appendChild(userinfoDIV); + + var userActivityDIV = document.createElement('div'); + userActivityDIV.className = 'user-activity'; + + userActivityDIV.innerHTML = '

' + args.header + '

'; + + var p = document.createElement('p'); + p.className = 'message'; + userActivityDIV.appendChild(p); + p.innerHTML = args.message; + + newMessageDIV.appendChild(userActivityDIV); + + main.insertBefore(newMessageDIV, main.firstChild); + + userinfoDIV.style.height = newMessageDIV.clientHeight + 'px'; + + if (args.callback) { + args.callback(newMessageDIV); + } + + document.querySelector('#message-sound').play(); +} + +main.querySelector('#your-name').onkeyup = function(e) { + if (e.keyCode != 13) return; + main.querySelector('#continue').onclick(); +}; + +main.querySelector('#room-name').onkeyup = function(e) { + if (e.keyCode != 13) return; + main.querySelector('#continue').onclick(); +}; + +main.querySelector('#room-name').value = localStorage.getItem('room-name') || (Math.random() * 1000).toString().replace('.', ''); +if (localStorage.getItem('user-name')) { + main.querySelector('#your-name').value = localStorage.getItem('user-name'); +} + +main.querySelector('#continue').onclick = function() { + var yourName = this.parentNode.querySelector('#your-name'); + var roomName = this.parentNode.querySelector('#room-name'); + + if (!roomName.value || !roomName.value.length) { + roomName.focus(); + return alert('Your MUST Enter Room Name!'); + } + + localStorage.setItem('room-name', roomName.value); + localStorage.setItem('user-name', yourName.value); + + yourName.disabled = roomName.disabled = this.disabled = true; + + var username = yourName.value || 'Anonymous'; + + rtcMultiConnection.extra.username = username; + rtcMultiConnection.extra.color = getRandomColor(); + + addNewMessage({ + header: username, + message: 'Searching for existing rooms...', + userinfo: '' + }); + + var roomid = main.querySelector('#room-name').value; + rtcMultiConnection.channel = roomid; + + rtcMultiConnection.checkPresence(roomid, function(isRoomExists) { + if (!isRoomExists) { + addNewMessage({ + header: username, + message: 'No room found. Creating new room...

You can share following room-id with your friends: ', + userinfo: '' + }); + + rtcMultiConnection.open(roomid); + } else { + addNewMessage({ + header: username, + message: 'Room found. Joining the room...', + userinfo: '' + }); + rtcMultiConnection.join(roomid); + } + + var socket = rtcMultiConnection.getSocket(); + socket.on(rtcMultiConnection.socketCustomEvent, rtcMultiConnection.onCustomMessage); + + console.debug('room is present?', isRoomExists); + }); +}; + +function getUserinfo(blobURL, imageURL) { + return blobURL ? '' : ''; +} + +var isShiftKeyPressed = false; + +getElement('.main-input-box textarea').onkeydown = function(e) { + if (e.keyCode == 16) { + isShiftKeyPressed = true; + } +}; + +var numberOfKeys = 0; +getElement('.main-input-box textarea').onkeyup = function(e) { + numberOfKeys++; + if (numberOfKeys > 3) numberOfKeys = 0; + + if (!numberOfKeys) { + if (e.keyCode == 8) { + return rtcMultiConnection.send({ + stoppedTyping: true + }); + } + + rtcMultiConnection.send({ + typing: true + }); + } + + if (isShiftKeyPressed) { + if (e.keyCode == 16) { + isShiftKeyPressed = false; + } + return; + } + + + if (e.keyCode != 13) return; + + addNewMessage({ + header: rtcMultiConnection.extra.username, + message: 'Your Message:

' + linkify(this.value), + userinfo: getUserinfo(rtcMultiConnection.blobURLs[rtcMultiConnection.userid], 'images/chat-message.png'), + color: rtcMultiConnection.extra.color + }); + + rtcMultiConnection.send(this.value); + + this.value = ''; +}; + +getElement('#allow-webcam').onclick = function() { + this.disabled = true; + + var session = { + audio: true, + video: true + }; + + rtcMultiConnection.captureUserMedia(function(stream) { + var streamid = rtcMultiConnection.token(); + rtcMultiConnection.customStreams[streamid] = stream; + + rtcMultiConnection.sendMessage({ + hasCamera: true, + streamid: streamid, + session: session + }); + }, session); +}; + +getElement('#allow-mic').onclick = function() { + this.disabled = true; + var session = { + audio: true + }; + + rtcMultiConnection.captureUserMedia(function(stream) { + var streamid = rtcMultiConnection.token(); + rtcMultiConnection.customStreams[streamid] = stream; + + rtcMultiConnection.sendMessage({ + hasMic: true, + streamid: streamid, + session: session + }); + }, session); +}; + +getElement('#allow-screen').onclick = function() { + this.disabled = true; + var session = { + screen: true + }; + + rtcMultiConnection.captureUserMedia(function(stream) { + var streamid = rtcMultiConnection.token(); + rtcMultiConnection.customStreams[streamid] = stream; + + rtcMultiConnection.sendMessage({ + hasScreen: true, + streamid: streamid, + session: session + }); + }, session); +}; + +getElement('#share-files').onclick = function() { + var file = document.createElement('input'); + file.type = 'file'; + + file.onchange = function() { + rtcMultiConnection.send(this.files[0]); + }; + fireClickEvent(file); +}; + +function fireClickEvent(element) { + var evt = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true + }); + + element.dispatchEvent(evt); +} + +function bytesToSize(bytes) { + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes == 0) return '0 Bytes'; + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; +} diff --git a/RTCMultiConnection/demos/MultiRTC/ui.peer-connection.js b/RTCMultiConnection/demos/MultiRTC/ui.peer-connection.js new file mode 100755 index 00000000..716dd50e --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/ui.peer-connection.js @@ -0,0 +1,268 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection + +var rtcMultiConnection = new RTCMultiConnection(); + +rtcMultiConnection.enableFileSharing = true; + +rtcMultiConnection.customStreams = {}; +rtcMultiConnection.renegotiatedSessions = {}; + +rtcMultiConnection.sendCustomMessage = function(message) { + var socket = rtcMultiConnection.getSocket(); + socket.emit(rtcMultiConnection.socketCustomEvent, message); +}; + +rtcMultiConnection.session = { data: true }; + +rtcMultiConnection.sdpConstraints.mandatory = { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true +}; + +/* +// http://www.rtcmulticonnection.org/docs/fakeDataChannels/ +rtcMultiConnection.fakeDataChannels = true; +if(rtcMultiConnection.UA.Firefox) { +rtcMultiConnection.session.data = true; +} +*/ + +rtcMultiConnection.autoTranslateText = false; + +rtcMultiConnection.onopen = function(e) { + getElement('#allow-webcam').disabled = false; + getElement('#allow-mic').disabled = false; + getElement('#share-files').disabled = false; + getElement('#allow-screen').disabled = false; + + if(!event.extra.username) { + event.extra.username = 'userid:' + event.userid; + } + + addNewMessage({ + header: e.extra.username, + message: 'Data connection is opened between you and ' + e.extra.username + '.', + userinfo: getUserinfo(rtcMultiConnection.blobURLs[rtcMultiConnection.userid], 'images/info.png'), + color: '#B7ECB7' + }); + + numbersOfUsers.innerHTML = parseInt(numbersOfUsers.innerHTML) + 1; +}; + +var whoIsTyping = document.querySelector('#who-is-typing'); +rtcMultiConnection.onmessage = function(e) { + if (e.data.typing) { + whoIsTyping.innerHTML = e.extra.username + ' is typing ...'; + return; + } + + if (e.data.stoppedTyping) { + whoIsTyping.innerHTML = ''; + return; + } + + whoIsTyping.innerHTML = ''; + + addNewMessage({ + header: e.extra.username, + message: 'Text message from ' + e.extra.username + ':

' + (rtcMultiConnection.autoTranslateText ? linkify(e.data) + ' ( ' + linkify(e.original) + ' )' : linkify(e.data)), + userinfo: getUserinfo(rtcMultiConnection.blobURLs[e.userid], 'images/chat-message.png'), + color: e.extra.color + }); + document.title = e.data; +}; + +var sessions = { }; +rtcMultiConnection.onNewSession = function(session) { + if (sessions[session.sessionid]) return; + sessions[session.sessionid] = session; + + session.join(); + + addNewMessage({ + header: session.extra.username, + message: 'Making handshake with room owner....!', + userinfo: '', + color: session.extra.color + }); +}; + +rtcMultiConnection.onNewParticipant = function(participantId, userPreferences) { + rtcMultiConnection.acceptParticipationRequest(participantId, userPreferences); + + var user = rtcMultiConnection.peers[participantId]; + addNewMessage({ + header: 'New Participant', + message: 'A participant found. Accepting request of ' + user.extra.username + ' ( ' + participantId + ' )...', + userinfo: '', + color: user.extra.color + }); +}; + +rtcMultiConnection.onCustomMessage = function(message) { + if (message.hasCamera || message.hasScreen) { + var msg = message.extra.username + ' enabled webcam. ---- '; + + if (message.hasScreen) { + msg = message.extra.username + ' is ready to share screen. ---- '; + } + + addNewMessage({ + header: message.extra.username, + message: msg, + userinfo: '', + color: message.extra.color, + callback: function(div) { + div.querySelector('#preview').onclick = function() { + this.disabled = true; + + message.session.oneway = true; + rtcMultiConnection.sendMessage({ + renegotiate: true, + streamid: message.streamid, + session: message.session + }); + }; + + div.querySelector('#share-your-cam').onclick = function() { + this.disabled = true; + + if (!message.hasScreen) { + session = { audio: true, video: true }; + + rtcMultiConnection.captureUserMedia(function(stream) { + rtcMultiConnection.renegotiatedSessions[JSON.stringify(session)] = { + session: session, + stream: stream + }; + + rtcMultiConnection.addStream(stream, message.userid); + div.querySelector('#preview').onclick(); + }, session); + } + + if (message.hasScreen) { + var session = { screen: true }; + + rtcMultiConnection.captureUserMedia(function(stream) { + rtcMultiConnection.renegotiatedSessions[JSON.stringify(session)] = { + session: session, + stream: stream + } + + rtcMultiConnection.addStream(stream, message.userid); + div.querySelector('#preview').onclick(); + }, session); + } + }; + } + }); + } + + if (message.hasMic) { + addNewMessage({ + header: message.extra.username, + message: message.extra.username + ' enabled microphone. ---- ', + userinfo: '', + color: message.extra.color, + callback: function(div) { + div.querySelector('#listen').onclick = function() { + this.disabled = true; + message.session.oneway = true; + rtcMultiConnection.sendMessage({ + renegotiate: true, + streamid: message.streamid, + session: message.session + }); + }; + + div.querySelector('#share-your-mic').onclick = function() { + this.disabled = true; + + var session = { audio: true }; + + rtcMultiConnection.captureUserMedia(function(stream) { + rtcMultiConnection.renegotiatedSessions[JSON.stringify(session)] = { + session: session, + stream: stream + }; + + rtcMultiConnection.addStream(stream, message.userid); + div.querySelector('#listen').onclick(); + }, session); + }; + } + }); + } + + if (message.renegotiate) { + var customStream = rtcMultiConnection.customStreams[message.streamid]; + if (customStream) { + // rtcMultiConnection.peers[message.userid].peer.addStream(customStream); + rtcMultiConnection.renegotiate(message.userid); + } + } +}; + + +rtcMultiConnection.blobURLs = { }; +rtcMultiConnection.onstream = function(e) { + if (e.stream.getVideoTracks().length) { + rtcMultiConnection.blobURLs[e.userid] = e.streamid; + /* + if( document.getElementById(e.userid) ) { + document.getElementById(e.userid).muted = true; + } + */ + addNewMessage({ + header: e.extra.username, + message: e.extra.username + ' enabled webcam.', + userinfo: '
Download ' + file.name + '
'; + } else { + div.innerHTML = 'Download ' + file.name + '
'; + } + + setTimeout(function() { + div = div.parentNode.parentNode.parentNode; + div.querySelector('.user-info').style.height = div.querySelector('.user-activity').clientHeight + 'px'; + }, 10); +}; + +function updateLabel(progress, label) { + if (progress.position == -1) return; + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; +} diff --git a/RTCMultiConnection/demos/MultiRTC/ui.users-list.js b/RTCMultiConnection/demos/MultiRTC/ui.users-list.js new file mode 100755 index 00000000..4b6cea54 --- /dev/null +++ b/RTCMultiConnection/demos/MultiRTC/ui.users-list.js @@ -0,0 +1,20 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Experiments - github.com/muaz-khan/RTCMultiConnection + +var usersList = getElement('.users-list'); +var numbersOfUsers = getElement('.numbers-of-users'); + +numbersOfUsers.innerHTML = 1; + +var usersContainer = getElement('.users-container'); + +usersList.onclick = function() { + if (usersList.className.indexOf('selected') != -1) { + usersList.className = usersList.className.replace( / selected/g , ''); + usersContainer.style.display = 'none'; + } else { + usersList.className += ' selected'; + usersContainer.style.display = 'block'; + } +}; diff --git a/RTCMultiConnection/demos/Password-Protected-Rooms.html b/RTCMultiConnection/demos/Password-Protected-Rooms.html index 931376b2..d38a4f8e 100755 --- a/RTCMultiConnection/demos/Password-Protected-Rooms.html +++ b/RTCMultiConnection/demos/Password-Protected-Rooms.html @@ -79,8 +79,11 @@

Password+Protected+Rooms using RTCMultiConnection-v3.0

- - + + + + + @@ -106,7 +109,35 @@

Password+Protected+Rooms using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = document.getElementById('room-password').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'password-protected-rooms-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; + + var room_password = ''; + if (localStorage.getItem('rmc-room-password')) { + room_password = localStorage.getItem('rmc-room-password'); + } else { + room_password = connection.token(); + } + document.getElementById('room-password').value = room_password; + document.getElementById('room-password').onkeyup = function() { + localStorage.setItem('rmc-room-password', this.value); + }; connection.session = { audio: true, @@ -119,9 +150,13 @@

Password+Protected+Rooms using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; connection.onJoinWithPassword = function(remoteUserId) { diff --git a/RTCMultiConnection/demos/PubNub-Demo.html b/RTCMultiConnection/demos/PubNub-Demo.html index c9a01c1d..dcd84bbb 100755 --- a/RTCMultiConnection/demos/PubNub-Demo.html +++ b/RTCMultiConnection/demos/PubNub-Demo.html @@ -72,8 +72,11 @@

PubNub Video Conferencing Demo using RTCMultiConnection-v3.0

- - + + + + + + + + + + +
+

Latest Updates

+
RecordRTC, RTCMultiConnection, MultiStreamsMixer, DetectRTC, Canvas-Designer, Chrome-Extensions updated.
MultiStreamsMixer, DetectRTC, RecordRTC, RTCMultiConnection updated.
+
+ +
+

Latest Issues

+

Not an issue, but...


Not sure anyone uses this code anymore? I think Muaz gave it up to Covid and is no longer with us. I have his Scalable Broadcasting working ...
+
+ +
+ + + + + + + + + + + + + + diff --git a/RTCMultiConnection/demos/TextChat+FileSharing.html b/RTCMultiConnection/demos/TextChat+FileSharing.html index a471057a..e5662bcb 100755 --- a/RTCMultiConnection/demos/TextChat+FileSharing.html +++ b/RTCMultiConnection/demos/TextChat+FileSharing.html @@ -72,8 +72,11 @@

TextChat+FileSharing using RTCMultiConnection-v3.0

- - + + + + + @@ -139,7 +142,24 @@

TextChat+FileSharing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'textchat-plus-fileshare-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.enableFileSharing = true; // by default, it is "false". diff --git a/RTCMultiConnection/demos/Video-Conferencing.html b/RTCMultiConnection/demos/Video-Conferencing.html index 033ef897..92d046e3 100755 --- a/RTCMultiConnection/demos/Video-Conferencing.html +++ b/RTCMultiConnection/demos/Video-Conferencing.html @@ -4,6 +4,10 @@ + + + + Video Conferencing using RTCMultiConnection-v3.0 @@ -12,6 +16,9 @@ @@ -56,7 +73,7 @@

Video Conferencing using RTCMultiConnection-v3.0

- + @@ -65,7 +82,8 @@

Video Conferencing using RTCMultiConnection-v3.0

- + + @@ -78,16 +96,22 @@

Video Conferencing using RTCMultiConnection-v3.0

document.getElementById('open-room').onclick = function() { this.disabled = true; connection.open(document.getElementById('room-id').value); + + document.getElementById('room-id').onkeyup(); }; document.getElementById('join-room').onclick = function() { this.disabled = true; connection.join(document.getElementById('room-id').value); + + document.getElementById('room-id').onkeyup(); }; document.getElementById('open-or-join-room').onclick = function() { this.disabled = true; connection.openOrJoin(document.getElementById('room-id').value); + + document.getElementById('room-id').onkeyup(); }; // ...................................................... @@ -96,7 +120,25 @@

Video Conferencing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'video-conference-demo'; + + var roomid = ''; + if(localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } + else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -108,9 +150,13 @@

Video Conferencing using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; @@ -151,4 +197,4 @@

Feedback

- + \ No newline at end of file diff --git a/RTCMultiConnection/demos/Video-Scalable-Broadcast.html b/RTCMultiConnection/demos/Video-Scalable-Broadcast.html index 6d4d2f3f..0e2b2ce9 100755 --- a/RTCMultiConnection/demos/Video-Scalable-Broadcast.html +++ b/RTCMultiConnection/demos/Video-Scalable-Broadcast.html @@ -119,23 +119,23 @@

WebRTC Scal This module simply initializes socket.io and configures it in a way that single audio/video/screen stream can be shared/relayed over unlimited users without any bandwidth/CPU usage issues. Everything happens peer-to-peer!

- Share files with unlimited users using p2p methods! Files Scalable Broadcast + Share files with unlimited users using p2p methods! Files Scalable Broadcast or all-in-one scalable broadcast. + + +
+ +
+

addStream in a Chat Room using RTCMultiConnection-v3.0

+

+ HOME + © + Muaz Khan . + @WebRTCWeb . + Github . + Latest issues . + What's New? +

+
+ + + + +
+
+ + + + + +

+ + + + +
+ +
+
+
+
+ +
+
+ + + + + + + + + + + + +
+

Latest Updates

+
+
+ +
+

Latest Issues

+
+
+ +
+ + + + + + +
+ + + + + + diff --git a/RTCMultiConnection/demos/applyConstraints.html b/RTCMultiConnection/demos/applyConstraints.html index 072b1788..ff82c551 100755 --- a/RTCMultiConnection/demos/applyConstraints.html +++ b/RTCMultiConnection/demos/applyConstraints.html @@ -75,8 +75,8 @@

MediaStreamTrack.applyConstraints demo using RTCMultiConnection-v3.0

- - + + @@ -107,9 +107,24 @@

MediaStreamTrack.applyConstraints demo using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; - connection.enableFileSharing = true; // by default, it is "false". + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'applyConstraints-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -121,9 +136,13 @@

MediaStreamTrack.applyConstraints demo using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; // ...................................................... diff --git a/RTCMultiConnection/demos/checkPresence.html b/RTCMultiConnection/demos/checkPresence.html index 6d5fd8e4..fc920772 100755 --- a/RTCMultiConnection/demos/checkPresence.html +++ b/RTCMultiConnection/demos/checkPresence.html @@ -69,7 +69,8 @@

Check Room Presence using RTCMultiConnection-v3.0

- + + @@ -121,7 +122,24 @@

Check Room Presence using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'checkPresence-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -133,9 +151,13 @@

Check Room Presence using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; diff --git a/RTCMultiConnection/demos/custom-socket-event.html b/RTCMultiConnection/demos/custom-socket-event.html index ed2406ef..f1654cca 100755 --- a/RTCMultiConnection/demos/custom-socket-event.html +++ b/RTCMultiConnection/demos/custom-socket-event.html @@ -72,7 +72,8 @@

Custom+Socket+Event using RTCMultiConnection-v3.0

- + + @@ -115,7 +116,24 @@

Custom+Socket+Event using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'custom-socket-event-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -127,9 +145,13 @@

Custom+Socket+Event using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; // ...................................................... diff --git a/RTCMultiConnection/demos/file-sharing.html b/RTCMultiConnection/demos/file-sharing.html index a737dcb5..8c2eabcf 100755 --- a/RTCMultiConnection/demos/file-sharing.html +++ b/RTCMultiConnection/demos/file-sharing.html @@ -1,4 +1,4 @@ - + File Sharing using RTCMultiConnection-v3.0 @@ -6,13 +6,6 @@ - - @@ -22,110 +15,321 @@ @@ -135,12 +339,38 @@ -
-
0
-
+
+

+ +

+
- - +
0
+
+

+ Peer-to-Peer (private) file sharing. +

+

+ You can share/receive files from any platform/device e.g. destkop operating systems, Android, iOS etc. +

+

+ Create or join a room & select file using "+" button. +

+
+ + + + + + + + + + + @@ -148,293 +378,372 @@ // Muaz Khan - https://github.com/muaz-khan // MIT License - https://www.WebRTC-Experiment.com/licence/ // Source Code - https://github.com/muaz-khan/RTCMultiConnection - var iframe = document.querySelector('iframe'); - var btnSelectFile = document.querySelector('.btn-select-file'); - - btnSelectFile.onclick = function() { - var fileSelector = new FileSelector(); - fileSelector.selectSingleFile(function(file) { - previewFile(file); + window.addEventListener('load', function() { + if (window.localStorage.getItem('room-id')) { + document.getElementById('room-id').value = window.localStorage.getItem('room-id'); + } else { + document.getElementById('room-id').value = (Math.random() * 100).toString().replace('.', ''); + } - onFileSelected(file); - }); - }; + if(location.hash.length > 1) { + document.getElementById('room-id').value = location.hash.replace('#', ''); + } - var connection; - var lastSelectedFile; + document.getElementById('join-room').onclick = function() { + this.disabled = true; + document.getElementById('room-id').disabled = true; - var room_id = ''; + var roomId = document.getElementById('room-id').value; + window.localStorage.setItem('room-id', roomId); - // 60k -- assuming receiving client is chrome - var chunk_size = 60 * 1000; + joinARoom(roomId); + }; - function setupWebRTCConnection() { - if (connection) { - return; + if(location.hash.length > 1) { + document.getElementById('join-room').disabled = true; + document.getElementById('room-id').disabled = true; + joinARoom(location.hash.replace('#', '')); } + }); - // www.RTCMultiConnection.org/docs/ - connection = new RTCMultiConnection(); + function joinARoom(roomId) { + var iframe = document.querySelector('iframe'); - // to make sure, "connection-reconnect" doesn't sends files again - connection.fileReceived = {}; + var btnSelectFile = document.querySelector('.btn-select-file'); - connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; - connection.socketMessageEvent = 'file-sharing'; - - connection.chunkSize = chunk_size; + btnSelectFile.onclick = function() { + var fileSelector = new FileSelector(); + fileSelector.selectSingleFile(function(file) { + previewFile(file); - connection.sdpConstraints.mandatory = { - OfferToReceiveAudio: false, - OfferToReceiveVideo: false + onFileSelected(file); + }); }; - connection.enableFileSharing = true; + var connection; + var lastSelectedFile; - if (room_id && room_id.length) { - connection.userid = room_id; - } + var room_id = ''; - connection.channel = connection.sessionid = location.hash.replace('#', ''); + // 60k -- assuming receiving client is chrome + var chunk_size = 60 * 1000; - connection.session = { - data: true, - // oneway: true --- to make it one-to-many - }; + function setupWebRTCConnection() { + if (connection) { + return; + } - connection.filesContainer = logsDiv; + // www.RTCMultiConnection.org/docs/ + connection = new RTCMultiConnection(); - connection.connectedWith = {}; + // to make sure, "connection-reconnect" doesn't sends files again + connection.fileReceived = {}; - connection.onopen = function(e) { - if(connection.connectedWith[e.userid]) return; - connection.connectedWith[e.userid] = true; + // by default, socket.io server is assumed to be deployed on your own URL + // connection.socketURL = '/'; - var message = '' + e.userid + '
is connected.'; - appendLog(message); + // comment-out below line if you do not have your own socket.io server + connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; - if (!lastSelectedFile) return; + connection.socketMessageEvent = 'file-sharing-demo'; - // already shared the file + connection.chunkSize = chunk_size; - var file = lastSelectedFile; - setTimeout(function() { - appendLog('Sharing file
' + file.name + 'Size: ' + bytesToSize(file.size) + '
With ' + connection.getAllParticipants().length + ' users'); - connection.shareFile(file); - }, 500); - }; + connection.sdpConstraints.mandatory = { + OfferToReceiveAudio: false, + OfferToReceiveVideo: false + }; - connection.onclose = function(e) { - incrementOrDecrementUsers(); + connection.enableFileSharing = true; - if(connection.connectedWith[e.userid]) return; + if (room_id && room_id.length) { + connection.userid = room_id; + } - appendLog('Data connection has been closed between you and ' + e.userid + '. Re-Connecting..'); - }; + connection.channel = connection.sessionid = roomId; - connection.onerror = function(e) { - if(connection.connectedWith[e.userid]) return; + connection.session = { + data: true, + // oneway: true --- to make it one-to-many + }; - appendLog('Data connection failed. between you and ' + e.userid + '. Retrying..'); - }; + connection.filesContainer = logsDiv; - setFileProgressBarHandlers(connection); + connection.connectedWith = {}; - connection.onUserStatusChanged = function(user) { - incrementOrDecrementUsers(); - }; + connection.onmessage = function(event) { + if(event.data.doYouWannaReceiveThisFile) { + if(!connection.fileReceived[event.data.fileName]) { + connection.send({ + yesIWannaReceive:true, + fileName: event.data.fileName + }); + } + } - connection.onleave = function(user) { - user.status = 'offline'; - connection.onUserStatusChanged(user); - incrementOrDecrementUsers(); - }; + if(event.data.yesIWannaReceive && !!lastSelectedFile) { + connection.shareFile(lastSelectedFile, event.userid); + } + }; + + connection.onopen = function(e) { + try { + chrome.power.requestKeepAwake('display'); + } + catch(e) {} + + if (connection.connectedWith[e.userid]) return; + connection.connectedWith[e.userid] = true; + + var message = '' + e.userid + '
is connected.'; + appendLog(message); + + if (!lastSelectedFile) return; + + // already shared the file + + var file = lastSelectedFile; + setTimeout(function() { + appendLog('Sharing file
' + file.name + '
Size: ' + bytesToSize(file.size) + '
With ' + connection.getAllParticipants().length + ' users'); + + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: file.name + }); + }, 500); + }; + + connection.onclose = function(e) { + incrementOrDecrementUsers(); - var message = 'Connecting room:
' + connection.channel + ''; - appendLog(message); + if (connection.connectedWith[e.userid]) return; - connection.openOrJoin(connection.channel, function(isRoomExists, roomid) { - var message = 'Successfully connected to room:
' + roomid + ''; - // if (isRoomEists) { } + appendLog('Data connection has been closed between you and ' + e.userid + '. Re-Connecting..'); + connection.join(roomId); + }; + + connection.onerror = function(e) { + if (connection.connectedWith[e.userid]) return; + + appendLog('Data connection failed. between you and ' + e.userid + '. Retrying..'); + }; + + setFileProgressBarHandlers(connection); + + connection.onUserStatusChanged = function(user) { + incrementOrDecrementUsers(); + }; + + connection.onleave = function(user) { + user.status = 'offline'; + connection.onUserStatusChanged(user); + incrementOrDecrementUsers(); + }; + + var message = 'Connecting room:
' + connection.channel + ''; appendLog(message); - }); - var resultingURL = location.href; + connection.openOrJoin(connection.channel, function(isRoomExists, roomid) { + var message = 'Successfully connected to room:
' + roomid + ''; + // if (isRoomEists) { } + appendLog(message); - document.querySelector('header').innerHTML = "Right-click to copy & share this private URL!"; - } + if(document.getElementById('room-id')) { + if(innerWidth > 500) { + document.getElementById('room-id').parentNode.innerHTML = 'Joined room: ' + roomid; + } + else { + document.getElementById('room-id').parentNode.innerHTML = 'Joined room:
' + roomid; + } + } + }); - function setFileProgressBarHandlers(connection) { - var progressHelper = {}; + window.connection = connection; + } - // www.RTCMultiConnection.org/docs/onFileStart/ - connection.onFileStart = function(file) { - if(connection.fileReceived[file.name]) return; + function setFileProgressBarHandlers(connection) { + var progressHelper = {}; - var div = document.createElement('div'); - div.id = file.uuid; - div.title = file.name; - div.innerHTML = ' '; + // www.RTCMultiConnection.org/docs/onFileStart/ + connection.onFileStart = function(file) { + if (connection.fileReceived[file.name]) return; - if (file.userid == connection.userid) { - div.innerHTML += ' (Sharing with:' + file.remoteUserId + ')'; - } - else { - div.innerHTML += ' (Receiving from:' + file.remoteUserId + ')'; - } + var div = document.createElement('div'); + div.style.borderBottom = '1px solid black'; + div.style.padding = '2px 4px'; + div.id = file.uuid; - connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + var message = ''; + if (file.userid == connection.userid) { + message += 'Sharing with:' + file.remoteUserId; + } else { + message += 'Receiving from:' + file.userid; + } - if (!file.remoteUserId) { - progressHelper[file.uuid] = { + message += '
' + file.name + '.'; + message += '
Size: ' + bytesToSize(file.size) + ''; + message += '
'; + + div.innerHTML = message; + + connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + + if (!file.remoteUserId) { + progressHelper[file.uuid] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid].progress.max = file.maxChunks; + return; + } + + if (!progressHelper[file.uuid]) { + progressHelper[file.uuid] = {}; + } + + progressHelper[file.uuid][file.remoteUserId] = { div: div, progress: div.querySelector('progress'), label: div.querySelector('label') }; - progressHelper[file.uuid].progress.max = file.maxChunks; - return; - } - - if (!progressHelper[file.uuid]) { - progressHelper[file.uuid] = {}; - } - - progressHelper[file.uuid][file.remoteUserId] = { - div: div, - progress: div.querySelector('progress'), - label: div.querySelector('label') + progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; }; - progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; - }; - // www.RTCMultiConnection.org/docs/onFileProgress/ - connection.onFileProgress = function(chunk) { - if(connection.fileReceived[chunk.name]) return; + // www.RTCMultiConnection.org/docs/onFileProgress/ + connection.onFileProgress = function(chunk) { + if (connection.fileReceived[chunk.name]) return; - var helper = progressHelper[chunk.uuid]; - if (!helper) { - return; - } - if (chunk.remoteUserId) { - helper = progressHelper[chunk.uuid][chunk.remoteUserId]; + var helper = progressHelper[chunk.uuid]; if (!helper) { return; } - } + if (chunk.remoteUserId) { + helper = progressHelper[chunk.uuid][chunk.remoteUserId]; + if (!helper) { + return; + } + } - helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; - updateLabel(helper.progress, helper.label); - }; + helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; + updateLabel(helper.progress, helper.label); + }; - // www.RTCMultiConnection.org/docs/onFileEnd/ - connection.onFileEnd = function(file) { - if(connection.fileReceived[file.name]) return; + // www.RTCMultiConnection.org/docs/onFileEnd/ + connection.onFileEnd = function(file) { + if (connection.fileReceived[file.name]) return; - var div = document.getElementById(file.uuid); - if (div) { - div.parentNode.removeChild(div); - } + var div = document.getElementById(file.uuid); + if (div) { + div.parentNode.removeChild(div); + } - if (file.remoteUserId === connection.userid) { - previewFile(file); + if (file.remoteUserId === connection.userid) { + previewFile(file); + + connection.fileReceived[file.name] = file; - connection.fileReceived[file.name] = file; + var message = 'Successfully received file'; + message += '
' + file.name + '.'; + message += '
Size: ' + bytesToSize(file.size) + '.'; + message += '
Download'; + var div = appendLog(message); + return; + } - var message = 'Successfully received file'; + var message = 'Successfully shared file'; message += '
' + file.name + '.'; + message += '
With: ' + file.remoteUserId + '.'; message += '
Size: ' + bytesToSize(file.size) + '.'; - message += '
Download'; appendLog(message); - return; - } + }; - var message = 'Successfully shared file'; - message += '
' + file.name + '.'; - message += '
With: ' + file.remoteUserId + '.'; - message += '
Size: ' + bytesToSize(file.size) + '.'; - appendLog(message); - }; + function updateLabel(progress, label) { + if (progress.position === -1) { + return; + } - function updateLabel(progress, label) { - if (progress.position === -1) { - return; + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; } - - var position = +progress.position.toFixed(2).split('.')[1] || 100; - label.innerHTML = position + '%'; } - } - function bytesToSize(bytes) { - var k = 1000; - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; - if (bytes === 0) { - return '0 Bytes'; + function bytesToSize(bytes) { + var k = 1000; + var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + if (bytes === 0) { + return '0 Bytes'; + } + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); + return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; } - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); - return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; - } - function onFileSelected(file) { - var innerHTML = 'You selected:
' + file.name + '
Size: ' + bytesToSize(file.size) + ''; - appendLog(innerHTML); + function onFileSelected(file) { + var innerHTML = 'You selected:
' + file.name + '
Size: ' + bytesToSize(file.size) + ''; + appendLog(innerHTML); - lastSelectedFile = file; + lastSelectedFile = file; - if (connection) { - connection.shareFile(file); + if (connection) { + connection.send({ + doYouWannaReceiveThisFile: true, + fileName: file.name + }); + } } - } - var numberOfUsers = document.getElementById('number-of-users'); + var numberOfUsers = document.getElementById('number-of-users'); - function incrementOrDecrementUsers() { - numberOfUsers.innerHTML = connection ? connection.getAllParticipants().length : 0; - } + function incrementOrDecrementUsers() { + numberOfUsers.innerHTML = connection ? connection.getAllParticipants().length : 0; + } - var logsDiv = document.getElementById('logs'); + var logsDiv = document.getElementById('logs'); - function appendLog(html) { - var div = document.createElement('div'); - div.innerHTML = '

' + html + '

'; - logsDiv.insertBefore(div, logsDiv.firstChild); - } + function appendLog(html) { + var div = document.createElement('div'); + div.innerHTML = '

' + html + '

'; + logsDiv.insertBefore(div, logsDiv.firstChild); - function previewFile(file) { - btnSelectFile.style.left = '5px'; - btnSelectFile.style.right = 'auto'; - btnSelectFile.style.zIndex = 10; - btnSelectFile.style.top = '5px'; - btnSelectFile.style.outline = 'none'; + return div; + } - document.querySelector('.overlay').style.display = 'none'; - iframe.style.display = 'block'; + function previewFile(file) { + btnSelectFile.style.left = '5px'; + btnSelectFile.style.right = 'auto'; + btnSelectFile.style.zIndex = 10; + btnSelectFile.style.top = '5px'; + btnSelectFile.style.outline = 'none'; - if (file.type.match(/image|video|audio|pdf|txt|javascript|css|php|py/g)) { - iframe.src = URL.createObjectURL(file); - } else { - iframe.src = 'https://i.imgur.com/2SUIhbf.png?1'; - } + document.querySelector('.overlay').style.display = 'none'; + iframe.style.display = 'block'; - iframe.onload = function() { - Array.prototype.slice.call(iframe.contentWindow.document.body.querySelectorAll('*')).forEach(function(element) { - element.style.maxWidth = '100%'; - }); - - if (file.type.match(/image|video|audio|pdf/g)) { - iframe.contentWindow.document.body.style.textAlign = 'center'; - iframe.contentWindow.document.body.style.background = 'black'; - iframe.contentWindow.document.body.style.color = 'white'; - return; + if (file.type.match(/image|video|audio|pdf|txt|javascript|css|php|py/g)) { + iframe.src = URL.createObjectURL(file); + } else { + iframe.src = 'https://cdn.webrtc-experiment.com/images/folder-icon.png'; } - iframe.contentWindow.document.body.style.textAlign = 'left'; - iframe.contentWindow.document.body.style.background = 'white'; - iframe.contentWindow.document.body.style.color = 'black'; - }; - } - setupWebRTCConnection(); + iframe.onload = function() { + Array.prototype.slice.call(iframe.contentWindow.document.body.querySelectorAll('*')).forEach(function(element) { + element.style.maxWidth = '100%'; + }); + + if (file.type.match(/image|video|audio|pdf/g)) { + iframe.contentWindow.document.body.style.textAlign = 'center'; + iframe.contentWindow.document.body.style.background = 'black'; + iframe.contentWindow.document.body.style.color = 'white'; + return; + } + iframe.contentWindow.document.body.style.textAlign = 'left'; + iframe.contentWindow.document.body.style.background = 'white'; + iframe.contentWindow.document.body.style.color = 'black'; + }; + } + + setupWebRTCConnection(); + } diff --git a/RTCMultiConnection/demos/getPublicModerators.html b/RTCMultiConnection/demos/getPublicModerators.html index 86a56ed7..1d187c96 100755 --- a/RTCMultiConnection/demos/getPublicModerators.html +++ b/RTCMultiConnection/demos/getPublicModerators.html @@ -69,7 +69,8 @@

List of all public moderators:

- + + @@ -105,7 +106,24 @@

List of all public moderators:

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'getPublicModerators-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -117,9 +135,13 @@

List of all public moderators:

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; var publicRoomsDiv = document.getElementById('public-rooms'); diff --git a/RTCMultiConnection/demos/index.html b/RTCMultiConnection/demos/index.html index ec0d3d34..21d7c6fc 100755 --- a/RTCMultiConnection/demos/index.html +++ b/RTCMultiConnection/demos/index.html @@ -12,7 +12,7 @@ - RTCMultiConnection-v3.0 Demos + RTCMultiConnection-v3 Demos @@ -22,7 +22,7 @@
-

RTCMultiConnection-v3.0 Demos / LIVE Demos

+

RTCMultiConnection-v3 Demos / LIVE Demos

HOME © @@ -37,16 +37,34 @@

RTCMultiConnection
-

RTCMultiConnection-v3.0 Demos

+

+ RTCMultiConnection-v3 Demos + + + + +

  1. - Switch/Change Cameras in a Live Conference + Share Audio+Video+TextChat+FileSharing between multiple users
  2. - +
  3. Share files with single or multiple users
  4. +
  5. + Scalable Media Broadcast over Unlimited users! +
  6. + +
  7. + Scalable OneWay Video Broadcasting +
  8. + +
  9. + Scalable OneWay File Sharing +
  10. +
  11. Setup Video Conferencing between multiple users
  12. @@ -60,11 +78,15 @@

    RTCMultiConnection-v3.0 Demos

  13. - Share Audio+Video+TextChat+FileSharing between multiple users + Share TextChat+FileSharing among multiple users
  14. - Share TextChat+FileSharing among multiple users + addStream in a Chat Room +
  15. + +
  16. + Share part of screen
  17. @@ -112,11 +134,11 @@

    RTCMultiConnection-v3.0 Demos

  18. - Scalable OneWay Video Broadcasting + Switch/Change Cameras in a Live Conference
  19. - Scalable OneWay File Sharing + MultiRTC / Skype-like app
@@ -128,35 +150,22 @@

RTCMultiConnection-v3.0 Demos

- How to install v3.0? - - - + How to install v3?

 sudo npm install rtcmulticonnection-v3
 
 # or MOST preferred one
-mkdir RTCMultiConnection-v3.0 && cd RTCMultiConnection-v3.0
+mkdir RTCMultiConnection-v3 && cd RTCMultiConnection-v3
 wget http://dl.webrtc-experiment.com/rtcmulticonnection-v3.tar.gz
 tar -zxvf rtcmulticonnection-v3.tar.gz
 ls -a
 node server.js
 
+

RTCMultiConnection v3 API Documentation

-
-

How to Configure v3.0?

- -
- wiki/Configure-v3.0 - -

-

- Open-Sourced as well! -

-
diff --git a/RTCMultiConnection/demos/replaceTrack.html b/RTCMultiConnection/demos/replaceTrack.html index 1340c7de..4daaebf6 100755 --- a/RTCMultiConnection/demos/replaceTrack.html +++ b/RTCMultiConnection/demos/replaceTrack.html @@ -62,14 +62,15 @@

RTPSender.replaceTrack using RTCMultiConnection-v3.0



- + +
- - + + @@ -100,9 +101,24 @@

RTPSender.replaceTrack using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; - connection.enableFileSharing = true; // by default, it is "false". + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'replaceTrack-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -114,10 +130,15 @@

RTPSender.replaceTrack using RTCMultiConnection-v3.0

OfferToReceiveVideo: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; + // ...................................................... // ..................replaceTrack Code................... // ...................................................... @@ -128,6 +149,14 @@

RTPSender.replaceTrack using RTCMultiConnection-v3.0

screen: true, oneway: true }); + document.getElementById('reset-track').disabled = false; + }; + document.getElementById('reset-track').onclick = function() { + this.disabled = true; + + // null: means reset-track with all users + // true: means reset video tracks + connection.resetTrack(null, true); }; diff --git a/RTCMultiConnection/demos/screen-sharing.html b/RTCMultiConnection/demos/screen-sharing.html index f1440277..523d4083 100755 --- a/RTCMultiConnection/demos/screen-sharing.html +++ b/RTCMultiConnection/demos/screen-sharing.html @@ -69,7 +69,8 @@

Screen Sharing using RTCMultiConnection-v3.0

- + + @@ -100,7 +101,24 @@

Screen Sharing using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'screen-sharing-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { screen: true, @@ -112,9 +130,13 @@

Screen Sharing using RTCMultiConnection-v3.0

OfferToReceiveVideo: false }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; diff --git a/RTCMultiConnection/demos/share-part-of-screen.html b/RTCMultiConnection/demos/share-part-of-screen.html new file mode 100755 index 00000000..529ab83a --- /dev/null +++ b/RTCMultiConnection/demos/share-part-of-screen.html @@ -0,0 +1,210 @@ + + + + + + + + + Share Part of Screen using RTCMultiConnection-v3.0 + + + + + + + + +
+ +
+

Share Part of Screen using RTCMultiConnection-v3.0

+

+ HOME + © + Muaz Khan . + @WebRTCWeb . + Github . + Latest issues . + What's New? +

+
+ +
+ +
+
+ + + + + +

+ +
+ +
+ +
+

Content of this DIV is editable and resize-able!

+

+ Open a room and click "Shre below entire DIV" blue button. Now your div changes will be auto shared with any user who joined your room. +

+ +
Simple input:
+
+
+ + + + + + + + + + +
+

Latest Updates

+
+
+ +
+

Latest Issues

+
+
+ +
+ + + + + + +
+ + + + + + diff --git a/RTCMultiConnection/demos/switch-cameras.html b/RTCMultiConnection/demos/switch-cameras.html index ebea76c8..c1f7bd77 100755 --- a/RTCMultiConnection/demos/switch-cameras.html +++ b/RTCMultiConnection/demos/switch-cameras.html @@ -84,7 +84,8 @@

Switch+Cameras using RTCMultiConnection-v3.0

- + + @@ -127,9 +128,26 @@

Switch+Cameras using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'switch-cameras-demo'; + connection.socketCustomEvent = connection.channel; - document.getElementById('room-id').value = connection.token(); + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, diff --git a/RTCMultiConnection/demos/video-broadcasting.html b/RTCMultiConnection/demos/video-broadcasting.html index 0ed1eaf0..d06ffd53 100755 --- a/RTCMultiConnection/demos/video-broadcasting.html +++ b/RTCMultiConnection/demos/video-broadcasting.html @@ -65,7 +65,8 @@

Video Broadcasting using RTCMultiConnection-v3.0

- + + @@ -104,7 +105,24 @@

Video Broadcasting using RTCMultiConnection-v3.0

var connection = new RTCMultiConnection(); - document.getElementById('room-id').value = connection.token(); + // by default, socket.io server is assumed to be deployed on your own URL + connection.socketURL = '/'; + + // comment-out below line if you do not have your own socket.io server + // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; + + connection.socketMessageEvent = 'video-broadcast-demo'; + + var roomid = ''; + if (localStorage.getItem('rmc-room-id')) { + roomid = localStorage.getItem('rmc-room-id'); + } else { + roomid = connection.token(); + } + document.getElementById('room-id').value = roomid; + document.getElementById('room-id').onkeyup = function() { + localStorage.setItem('rmc-room-id', this.value); + }; connection.session = { audio: true, @@ -112,9 +130,13 @@

Video Broadcasting using RTCMultiConnection-v3.0

oneway: true }; - var videosContainer = document.getElementById('videos-container'); + connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { - videosContainer.appendChild(event.mediaElement); + connection.videosContainer.appendChild(event.mediaElement); + event.mediaElement.play(); + setTimeout(function() { + event.mediaElement.play(); + }, 5000); }; diff --git a/RTCMultiConnection/dev/BandwidthHandler.js b/RTCMultiConnection/dev/BandwidthHandler.js index be599d37..af109fab 100755 --- a/RTCMultiConnection/dev/BandwidthHandler.js +++ b/RTCMultiConnection/dev/BandwidthHandler.js @@ -1,6 +1,15 @@ // BandwidthHandler.js var BandwidthHandler = (function() { + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + } + function setBAS(sdp, bandwidth, isScreen) { if (!bandwidth) { return sdp; @@ -10,6 +19,10 @@ var BandwidthHandler = (function() { return sdp; } + if (isMobileDevice) { + return sdp; + } + if (isScreen) { if (!bandwidth.screen) { console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); @@ -69,6 +82,10 @@ var BandwidthHandler = (function() { } function setVideoBitrates(sdp, params) { + if (isMobileDevice) { + return sdp; + } + params = params || {}; var xgoogle_min_bitrate = params.min; var xgoogle_max_bitrate = params.max; @@ -108,6 +125,10 @@ var BandwidthHandler = (function() { } function setOpusAttributes(sdp, params) { + if (isMobileDevice) { + return sdp; + } + params = params || {}; var sdpLines = sdp.split('\r\n'); diff --git a/RTCMultiConnection/dev/CodecsHandler.js b/RTCMultiConnection/dev/CodecsHandler.js new file mode 100755 index 00000000..a4a83649 --- /dev/null +++ b/RTCMultiConnection/dev/CodecsHandler.js @@ -0,0 +1,279 @@ +// CodecsHandler.js + +var CodecsHandler = (function() { + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + } + + // "removeVPX" and "removeNonG722" methods are taken from github/mozilla/webrtc-landing + function removeVPX(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + // this method is NOT reliable + + sdp = sdp.replace('a=rtpmap:100 VP8/90000\r\n', ''); + sdp = sdp.replace('a=rtpmap:101 VP9/90000\r\n', ''); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g, 'm=video $1 RTP\/SAVPF $2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g, 'm=video $1 RTP\/SAVPF $2'); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + + sdp = sdp.replace('a=rtcp-fb:120 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 ccm fir\r\n', ''); + + sdp = sdp.replace('a=rtcp-fb:101 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 ccm fir\r\n', ''); + + return sdp; + } + + function disableNACK(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n'); + sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n'); + + return sdp; + } + + function prioritize(codecMimeType, peer) { + if (!peer || !peer.getSenders || !peer.getSenders().length) { + return; + } + + if (!codecMimeType || typeof codecMimeType !== 'string') { + throw 'Invalid arguments.'; + } + + peer.getSenders().forEach(function(sender) { + var params = sender.getParameters(); + for (var i = 0; i < params.codecs.length; i++) { + if (params.codecs[i].mimeType == codecMimeType) { + params.codecs.unshift(params.codecs.splice(i, 1)); + break; + } + } + sender.setParameters(params); + }); + } + + function removeNonG722(sdp) { + return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9'); + } + + function setBAS(sdp, bandwidth, isScreen) { + if (!bandwidth) { + return sdp; + } + + if (typeof isFirefox !== 'undefined' && isFirefox) { + return sdp; + } + + if (isMobileDevice) { + return sdp; + } + + if (isScreen) { + if (!bandwidth.screen) { + console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); + } else if (bandwidth.screen < 300) { + console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); + } + } + + // if screen; must use at least 300kbs + if (bandwidth.screen && isScreen) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } + + // remove existing bandwidth lines + if (bandwidth.audio || bandwidth.video || bandwidth.data) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + } + + if (bandwidth.audio) { + sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + } + + if (bandwidth.video) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n'); + } + + return sdp; + } + + // Find the line in sdpLines that starts with |prefix|, and, if specified, + // contains |substr| (case-insensitive search). + function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); + } + + // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| + // and, if specified, contains |substr| (case-insensitive search). + function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; + } + } + } + return null; + } + + // Gets the codec payload type from an a=rtpmap:X line. + function getCodecPayloadType(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + function setVideoBitrates(sdp, params) { + if (isMobileDevice) { + return sdp; + } + + params = params || {}; + var xgoogle_min_bitrate = params.min; + var xgoogle_max_bitrate = params.max; + + var sdpLines = sdp.split('\r\n'); + + // VP8 + var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); + var vp8Payload; + if (vp8Index) { + vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); + } + + if (!vp8Payload) { + return sdp; + } + + var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); + var rtxPayload; + if (rtxIndex) { + rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); + } + + if (!rtxIndex) { + return sdp; + } + + var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); + if (rtxFmtpLineIndex !== null) { + var appendrtxNext = '\r\n'; + appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); + sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); + sdp = sdpLines.join('\r\n'); + } + + return sdp; + } + + function setOpusAttributes(sdp, params) { + if (isMobileDevice) { + return sdp; + } + + params = params || {}; + + var sdpLines = sdp.split('\r\n'); + + // Opus + var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); + var opusPayload; + if (opusIndex) { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + if (!opusPayload) { + return sdp; + } + + var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + if (opusFmtpLineIndex === null) { + return sdp; + } + + var appendOpusNext = ''; + appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); + appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); + + if (typeof params.maxaveragebitrate != 'undefined') { + appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); + } + + if (typeof params.maxplaybackrate != 'undefined') { + appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); + } + + if (typeof params.cbr != 'undefined') { + appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); + } + + if (typeof params.useinbandfec != 'undefined') { + appendOpusNext += '; useinbandfec=' + params.useinbandfec; + } + + if (typeof params.usedtx != 'undefined') { + appendOpusNext += '; usedtx=' + params.usedtx; + } + + if (typeof params.maxptime != 'undefined') { + appendOpusNext += '\r\na=maxptime:' + params.maxptime; + } + + sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + function preferVP9(sdp) { + if (sdp.indexOf('SAVPF 100 101') === -1 || sdp.indexOf('VP9/90000') === -1) { + return sdp; + } + + return sdp.replace('SAVPF 100 101', 'SAVPF 101 100'); + } + + return { + removeVPX: removeVPX, + disableNACK: disableNACK, + prioritize: prioritize, + removeNonG722: removeNonG722, + setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { + return setBAS(sdp, bandwidth, isScreen); + }, + setVideoBitrates: function(sdp, params) { + return setVideoBitrates(sdp, params); + }, + setOpusAttributes: function(sdp, params) { + return setOpusAttributes(sdp, params); + }, + preferVP9: preferVP9 + }; +})(); + +// backward compatibility +window.BandwidthHandler = CodecsHandler; diff --git a/RTCMultiConnection/dev/DetectRTC.js b/RTCMultiConnection/dev/DetectRTC.js index 728ba197..95fd7500 100644 --- a/RTCMultiConnection/dev/DetectRTC.js +++ b/RTCMultiConnection/dev/DetectRTC.js @@ -1,4 +1,4 @@ -// Last time updated at Monday, January 4th, 2016, 1:17:50 PM +// Last time updated: 2016-02-26 11:47:17 AM UTC // Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js @@ -377,22 +377,28 @@ var osVersion = unknown; if (/Windows/.test(os)) { - osVersion = /Windows (.*)/.exec(os)[1]; + if (/Windows (.*)/.test(os)) { + osVersion = /Windows (.*)/.exec(os)[1]; + } os = 'Windows'; } switch (os) { case 'Mac OS X': - osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]; + if (/Mac OS X (10[\.\_\d]+)/.test(nAgt)) { + osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]; + } break; - case 'Android': - osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]; + if (/Android ([\.\_\d]+)/.test(nAgt)) { + osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]; + } break; - case 'iOS': - osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer); - osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0); + if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) { + osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer); + osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0); + } break; } @@ -652,7 +658,9 @@ if (!device.label) { device.label = 'Please invoke getUserMedia once.'; if (location.protocol !== 'https:') { - device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; + if (document.domain.search && document.domain.search(/localhost|127.0./g) === -1) { + device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; + } } } else { if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) { @@ -893,7 +901,7 @@ if ('getSenders' in mozRTCPeerConnection.prototype) { isRTPSenderReplaceTracksSupported = true; } - } else if (DetectRTC.browser.isChrome) { + } else if (DetectRTC.browser.isChrome && typeof webkitRTCPeerConnection !== 'undefined') { /*global webkitRTCPeerConnection:true */ if ('getSenders' in webkitRTCPeerConnection.prototype) { isRTPSenderReplaceTracksSupported = true; diff --git a/RTCMultiConnection/dev/FileBufferReader.js b/RTCMultiConnection/dev/FileBufferReader.js index f6eaf14a..5401695e 100755 --- a/RTCMultiConnection/dev/FileBufferReader.js +++ b/RTCMultiConnection/dev/FileBufferReader.js @@ -38,7 +38,8 @@ function FileBufferReader() { uuid: file.uuid || 0, file: file, earlyCallback: earlyCallback, - extra: extra + extra: extra, + chunkSize: extra.chunkSize }; fbrHelper.readAsArrayBuffer(fbr, options); @@ -169,14 +170,6 @@ function FileBufferReaderHelper() { }; } - if (chunk.end) { - for (var f in options.file) { - if (![chunk[f]]) { - chunk[f] = options.file[f]; - } - } - } - options.extra = options.extra || { userid: 0 }; @@ -254,7 +247,8 @@ function FileBufferReaderHelper() { type: file.type, lastModifiedDate: !!file.lastModifiedDate ? file.lastModifiedDate.toString() : '', start: true, - extra: options + extra: options.extra || options, + url: URL.createObjectURL(file) }); var blob, reader = new FileReader(); @@ -280,7 +274,7 @@ function FileBufferReaderHelper() { url: URL.createObjectURL(file), type: file.type, end: true, - extra: options + extra: options.extra || options }); } }); @@ -308,7 +302,7 @@ function FileBufferReaderHelper() { name: file.name || options.extra.fileName, lastModifiedDate: !!file.lastModifiedDate ? file.lastModifiedDate.toString() : '', type: file.type, - extra: options + extra: options.extra || options }); currentPosition++; diff --git a/RTCMultiConnection/dev/IceServersHandler.js b/RTCMultiConnection/dev/IceServersHandler.js index 905a9f01..e4610df1 100755 --- a/RTCMultiConnection/dev/IceServersHandler.js +++ b/RTCMultiConnection/dev/IceServersHandler.js @@ -1,5 +1,4 @@ // IceServersHandler.js -// note: "urls" doesn't works in old-firefox. var iceFrame, loadedIceFrame; @@ -18,8 +17,6 @@ function loadIceFrame(callback, skip) { function iFrameLoaderCallback(event) { if (!event.data || !event.data.iceServers) return; callback(event.data.iceServers); - - // this event listener is no more needed window.removeEventListener('message', iFrameLoaderCallback); } @@ -30,7 +27,7 @@ function loadIceFrame(callback, skip) { (document.body || document.documentElement).appendChild(iframe); } -if (typeof window.getExternalIceServers === 'undefined' || window.getExternalIceServers == true) { +if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { loadIceFrame(function(externalIceServers) { if (!externalIceServers || !externalIceServers.length) return; window.RMCExternalIceServers = externalIceServers; @@ -41,46 +38,64 @@ if (typeof window.getExternalIceServers === 'undefined' || window.getExternalIce }); } -var IceServersHandler = (function() { - function getIceServers(connection) { - var iceServers = []; +function getSTUNObj(stunStr) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; + } + + var obj = {}; + obj[urlsParam] = stunStr; + return obj; +} + +function getTURNObj(turnStr, username, credential) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; + } + + var obj = { + username: username, + credential: credential + }; + obj[urlsParam] = turnStr; + return obj; +} - // Firefox <= 37 doesn't understands "urls" +function getExtenralIceFormatted() { + var iceServers; + window.RMCExternalIceServers.forEach(function(ice) { + if (!ice.urls) { + ice.urls = ice.url; + } - iceServers.push({ - urls: 'stun:stun.l.google.com:19302' - }); + if (ice.urls.search('stun|stuns') !== -1) { + iceServers.push(getSTUNObj(ice.urls)); + } - iceServers.push({ - urls: 'stun:stun.anyfirewall.com:3478' - }); + if (ice.urls.search('turn|turns') !== -1) { + iceServers.push(getTURNObj(ice.urls, ice.username, ice.credential)); + } + }); + return iceServers; +} - iceServers.push({ - urls: 'turn:turn.bistri.com:80', - credential: 'homeo', - username: 'homeo' - }); +var IceServersHandler = (function() { + function getIceServers(connection) { + var iceServers = []; - iceServers.push({ - urls: 'turn:turn.anyfirewall.com:443?transport=tcp', - credential: 'webrtc', - username: 'webrtc' - }); + iceServers.push(getSTUNObj('stun:stun.l.google.com:19302')); + iceServers.push(getTURNObj('turn:turn.bistri.com:80', 'homeo', 'homeo')); + iceServers.push(getTURNObj('turn:turn.anyfirewall.com:443', 'webrtc', 'webrtc')); if (window.RMCExternalIceServers) { - iceServers = window.RMCExternalIceServers.concat(iceServers); + iceServers = iceServers.concat(getExtenralIceFormatted()); + } else if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { connection.iceServers = iceServers; - } else if (typeof window.getExternalIceServers === 'undefined' || window.getExternalIceServers == true) { window.iceServersLoadCallback = function() { - iceServers = window.RMCExternalIceServers.concat(iceServers); - connection.iceServers = iceServers; + connection.iceServers = connection.iceServers.concat(getExtenralIceFormatted()); }; - } else { - iceServers.push({ - urls: 'turn:turn.anyfirewall.com:443?transport=udp', - credential: 'webrtc', - username: 'webrtc' - }); } return iceServers; diff --git a/RTCMultiConnection/dev/MultiPeersHandler.js b/RTCMultiConnection/dev/MultiPeersHandler.js index 1142438b..2bf51c08 100755 --- a/RTCMultiConnection/dev/MultiPeersHandler.js +++ b/RTCMultiConnection/dev/MultiPeersHandler.js @@ -86,21 +86,15 @@ function MultiPeers(connection) { return { streamsToShare: userPreferences.streamsToShare || {}, - session: connection.session, rtcMultiConnection: connection, connectionDescription: userPreferences.connectionDescription, - remoteUserId: remoteUserId, + userid: remoteUserId, localPeerSdpConstraints: userPreferences.localPeerSdpConstraints, remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints, dontGetRemoteStream: !!userPreferences.dontGetRemoteStream, dontAttachLocalStream: !!userPreferences.dontAttachLocalStream, - optionalArgument: connection.optionalArgument, - iceServers: connection.iceServers, renegotiatingPeer: !!userPreferences.renegotiatingPeer, peerRef: userPreferences.peerRef, - enableDataChannels: !!connection.session.data, - localStreams: connection.attachStreams, - removeStreams: connection.removeStreams, onLocalSdp: function(localSdp) { self.onNegotiationNeeded(localSdp, remoteUserId); }, @@ -153,15 +147,12 @@ function MultiPeers(connection) { onRemoteStream: function(stream) { connection.peers[remoteUserId].streams.push(stream); - if (isPluginRTC) { + if (isPluginRTC && window.PluginRTC) { var mediaElement = document.createElement('video'); - var body = (document.body || document.documentElement); + var body = connection.videosContainer; body.insertBefore(mediaElement, body.firstChild); - setTimeout(function() { - Plugin.attachMediaStream(mediaElement, stream); - - self.onGettingRemoteMedia(mediaElement, remoteUserId); + window.PluginRTC.attachMediaStream(mediaElement, stream); }, 3000); return; } @@ -186,8 +177,7 @@ function MultiPeers(connection) { self.onUserLeft(remoteUserId); self.disconnectWith(remoteUserId); } - }, - processSdp: connection.processSdp + } }; }; @@ -207,14 +197,14 @@ function MultiPeers(connection) { return; } - userPreferences = connection.setUserPreferences(userPreferences); + userPreferences = connection.setUserPreferences(userPreferences, remoteUserId); var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); }; this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) { - userPreferences = connection.setUserPreferences(userPreferences || {}); + userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId); var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); connection.peers[remoteUserId] = new PeerInitiator(localConfig); @@ -222,7 +212,10 @@ function MultiPeers(connection) { this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) { if (!connection.peers[remoteUserId]) { - throw 'This peer (' + remoteUserId + ') does not exists.'; + if (connection.enableLogs) { + console.error('This peer (' + remoteUserId + ') does not exists. Renegotiation skipped.'); + } + return; } if (!userPreferences) { @@ -247,10 +240,12 @@ function MultiPeers(connection) { if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) { peer.getSenders().forEach(function(rtpSender) { if (isVideoTrack && rtpSender.track instanceof VideoStreamTrack) { + connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track; rtpSender.replaceTrack(track); } if (!isVideoTrack && rtpSender.track instanceof AudioStreamTrack) { + connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track; rtpSender.replaceTrack(track); } }); @@ -296,7 +291,7 @@ function MultiPeers(connection) { } if (message.enableMedia) { - if (connection.attachStreams.length) { + if (connection.attachStreams.length || connection.dontCaptureUserMedia) { var streamsToShare = {}; connection.attachStreams.forEach(function(stream) { streamsToShare[stream.streamid] = { @@ -324,34 +319,7 @@ function MultiPeers(connection) { localMediaConstraints.video = connection.mediaConstraints.video; } - getUserMediaHandler({ - onGettingLocalMedia: function(localStream) { - self.onGettingLocalMedia(localStream); - - var streamsToShare = {}; - connection.attachStreams.forEach(function(stream) { - streamsToShare[stream.streamid] = { - isAudio: !!stream.isAudio, - isVideo: !!stream.isVideo, - isScreen: !!stream.isScreen - }; - }); - message.userPreferences.streamsToShare = streamsToShare; - - self.onNegotiationNeeded({ - readyForOffer: true, - userPreferences: message.userPreferences - }, remoteUserId); - }, - onLocalMediaError: function(error) { - self.onLocalMediaError(error); - self.onNegotiationNeeded({ - readyForOffer: true, - userPreferences: message.userPreferences - }, remoteUserId); - }, - localMediaConstraints: localMediaConstraints - }); + invokeGetUserMedia(localMediaConstraints, message, remoteUserId); } if (message.readyForOffer) { @@ -359,14 +327,77 @@ function MultiPeers(connection) { } }; + function invokeGetUserMedia(mediaConstraints, message, remoteUserId) { + getUserMediaHandler({ + onGettingLocalMedia: function(localStream) { + self.onGettingLocalMedia(localStream); + + var streamsToShare = {}; + connection.attachStreams.forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + message.userPreferences.streamsToShare = streamsToShare; + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + }, + onLocalMediaError: function(error, constraints) { + self.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, message, remoteUserId); + return; + } + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + }, + localMediaConstraints: mediaConstraints + }); + } + + + this.connectNewParticipantWithAllBroadcasters = function(newParticipantId, userPreferences, broadcastersList) { + broadcastersList = broadcastersList.split('|-,-|'); + if (!broadcastersList.length) { + return; + } + + var firstBroadcaster = broadcastersList[0]; + + self.onNegotiationNeeded({ + newParticipant: newParticipantId, + userPreferences: userPreferences || false + }, firstBroadcaster); + + delete broadcastersList[0]; + + var array = []; + broadcastersList.forEach(function(broadcaster) { + if (broadcaster) { + array.push(broadcaster); + } + }); + + setTimeout(function() { + self.connectNewParticipantWithAllBroadcasters(newParticipantId, userPreferences, array.join('|-,-|')); + }, 10 * 1000); + }; + this.onGettingRemoteMedia = function(stream, remoteUserId) {}; this.onRemovingRemoteMedia = function(stream, remoteUserId) {}; this.onGettingLocalMedia = function(localStream) {}; - this.onLocalMediaError = function(error) { - if (!!connection.enableLogs) { - console.error('onLocalMediaError', JSON.stringify(error, null, '\t')); - } - connection.onMediaError(error); + this.onLocalMediaError = function(error, constraints) { + connection.onMediaError(error, constraints); }; var fbr; @@ -408,7 +439,7 @@ function MultiPeers(connection) { }, { userid: connection.userid, // extra: connection.extra, - chunkSize: connection.chunkSize || 0 + chunkSize: isFirefox ? 15 * 1000 : connection.chunkSize || 0 }); }; @@ -433,6 +464,11 @@ function MultiPeers(connection) { }; this.onDataChannelOpened = function(channel, remoteUserId) { + // keep last channel only; we are not expecting parallel/channels channels + if (connection.peers[remoteUserId].channels.length) { + return; + } + connection.peers[remoteUserId].channels.push(channel); connection.onopen({ userid: remoteUserId, @@ -442,11 +478,7 @@ function MultiPeers(connection) { }; this.onPeerStateChanged = function(state) { - if (connection.enableLogs) { - if (state.iceConnectionState.search(/disconnected|closed|failed/gi) !== -1) { - console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState); - } - } + connection.onPeerStateChanged(state); }; this.onNegotiationStarted = function(remoteUserId, states) {}; diff --git a/RTCMultiConnection/dev/Plugin.EveryWhere.js b/RTCMultiConnection/dev/Plugin.EveryWhere.js index ade4351d..a482aed1 100755 --- a/RTCMultiConnection/dev/Plugin.EveryWhere.js +++ b/RTCMultiConnection/dev/Plugin.EveryWhere.js @@ -1,4 +1,4 @@ -// Last time updated at Feb 08, 2015, 08:32:23 +// Last time updated at March 15, 2016 // Latest file can be found here: https://cdn.webrtc-experiment.com/Plugin.EveryWhere.js @@ -12,11 +12,21 @@ // Original Source: https://github.com/sarandogou/webrtc-everywhere#downloads (function() { - var ua = navigator.userAgent.toLowerCase(); - var isSafari = ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1; - var isIE = !!((Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, "ActiveXObject")) || ("ActiveXObject" in window)); + var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob); + var isIE = !!document.documentMode && !isEdge; - if (!(isSafari || isIE)) return; + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + } + + if (navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + } + + if (!(isSafari || isIE) || isMobileDevice) return; function LoadPluginRTC() { window.PluginRTC = {}; @@ -24,38 +34,53 @@ var extractPluginObj = function(elt) { return elt.isWebRtcPlugin ? elt : elt.pluginObj; } + var attachEventListener = function(elt, type, listener, useCapture) { var _pluginObj = extractPluginObj(elt); if (_pluginObj) { _pluginObj.bindEventListener(type, listener, useCapture); } else { - if (typeof elt.addEventListener !== "undefined") { + if (typeof elt.addEventListener !== 'undefined') { elt.addEventListener(type, listener, useCapture); - } else if (typeof elt.addEvent !== "undefined") { - elt.addEventListener("on" + type, listener, useCapture); + } else if (typeof elt.addEvent !== 'undefined') { + elt.addEventListener('on' + type, listener, useCapture); } } } + var pluginUID = 'WebrtcEverywherePluginId'; + function getPlugin() { - return document.getElementById('WebrtcEverywherePluginId'); + return document.getElementById(pluginUID); } var installPlugin = function() { - if (document.getElementById('WebrtcEverywherePluginId')) { + if (document.getElementById(pluginUID)) { return; } + var isInternetExplorer = !!((Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, 'ActiveXObject')) || ('ActiveXObject' in window)); + var isSafari = !!navigator.userAgent.indexOf('Safari'); + var pluginObj = document.createElement('object'); - if (isIE) { + if (isInternetExplorer) { pluginObj.setAttribute('classid', 'CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53'); + isInternetExplorer = true; } else { pluginObj.setAttribute('type', 'application/webrtc-everywhere'); } - pluginObj.setAttribute('id', 'WebrtcEverywherePluginId'); - (document.body || document.documentElement).appendChild(pluginObj); + pluginObj.setAttribute('id', pluginUID); + document.body.appendChild(pluginObj); pluginObj.setAttribute('width', '0'); pluginObj.setAttribute('height', '0'); + + if (pluginObj.isWebRtcPlugin || (typeof navigator.plugins !== 'undefined' && (!!navigator.plugins['WebRTC Everywhere'] || navigator.plugins['WebRTC Everywhere Plug-in for Safari']))) { + if (isInternetExplorer) { + webrtcDetectedBrowser = 'Internet Explorer'; + } else if (isSafari) { + webrtcDetectedBrowser = 'Safari'; + } + } } if (document.body) { @@ -83,18 +108,28 @@ } }); } - } else getPlugin().getUserMedia(constraints, successCallback, errorCallback); + } else { + getPlugin().getUserMedia(constraints, successCallback, errorCallback); + } } window.PluginRTC.attachMediaStream = function(element, stream) { + if (!element) { + return null; + } if (element.isWebRtcPlugin) { element.src = stream; return element; } else if (element.nodeName.toLowerCase() === 'video') { if (!element.pluginObj && stream) { var _pluginObj = document.createElement('object'); - var _isIE = (Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, "ActiveXObject")) || ("ActiveXObject" in window); + var _isIE = (Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(window, 'ActiveXObject')) || ('ActiveXObject' in window); if (_isIE) { + // windowless + var windowlessParam = document.createElement('param'); + windowlessParam.setAttribute('name', 'windowless'); + windowlessParam.setAttribute('value', true); + _pluginObj.appendChild(windowlessParam); _pluginObj.setAttribute('classid', 'CLSID:7FD49E23-C8D7-4C4F-93A1-F7EACFA1EC53'); } else { _pluginObj.setAttribute('type', 'application/webrtc-everywhere'); @@ -120,14 +155,13 @@ } if (width) _pluginObj.setAttribute('width', width); else _pluginObj.setAttribute('autowidth', true); - if (height) _pluginObj.setAttribute('height', height); else _pluginObj.setAttribute('autoheight', true); - (document.body || document.documentElement).appendChild(_pluginObj); + document.body.appendChild(_pluginObj); if (element.parentNode) { element.parentNode.replaceChild(_pluginObj, element); // replace (and remove) element - + // add element again to be sure any query() will succeed document.body.appendChild(element); element.style.visibility = 'hidden'; } @@ -137,10 +171,10 @@ element.pluginObj.bindEventListener('play', function(objvid) { if (element.pluginObj) { if (element.pluginObj.getAttribute('autowidth') && objvid.videoWidth) { - element.pluginObj.setAttribute('width', objvid.videoWidth /* + "px"*/ ); + element.pluginObj.setAttribute('width', objvid.videoWidth /* + 'px'*/ ); } if (element.pluginObj.getAttribute('autoheight') && objvid.videoHeight) { - element.pluginObj.setAttribute('height', objvid.videoHeight /* + "px"*/ ); + element.pluginObj.setAttribute('height', objvid.videoHeight /* + 'px'*/ ); } } }); @@ -160,7 +194,7 @@ if (!getSourcesDelayed) { getSourcesDelayed = true; attachEventListener(document, 'readystatechange', function() { - if (getSourcesDelayed && document.readyState == "complete") { + if (getSourcesDelayed && document.readyState == 'complete') { getSourcesDelayed = false; getPlugin().getSources(gotSources); } @@ -169,19 +203,19 @@ } else { getPlugin().getSources(gotSources); } - }; + } window.PluginRTC.RTCPeerConnection = function(configuration, constraints) { return getPlugin().createPeerConnection(configuration, constraints); - }; + } window.PluginRTC.RTCIceCandidate = function(RTCIceCandidateInit) { return getPlugin().createIceCandidate(RTCIceCandidateInit); - }; + } window.PluginRTC.RTCSessionDescription = function(RTCSessionDescriptionInit) { return getPlugin().createSessionDescription(RTCSessionDescriptionInit); - }; + } if (window.onPluginRTCInitialized) { window.onPluginRTCInitialized(window.PluginRTC); diff --git a/RTCMultiConnection/dev/README.md b/RTCMultiConnection/dev/README.md new file mode 100755 index 00000000..dac276aa --- /dev/null +++ b/RTCMultiConnection/dev/README.md @@ -0,0 +1,7 @@ +# RTCMultiConnection Development files + +These are small pieces of JavaScript files that are used to generate `RTCMultiConnection.js`!!! + +# For contributions, you have to modify these files + +Then use `grunt` to compile `RTCMultiConnection.js` again. diff --git a/RTCMultiConnection/dev/RTCMultiConnection.js b/RTCMultiConnection/dev/RTCMultiConnection.js index 324acc02..134e6ad2 100755 --- a/RTCMultiConnection/dev/RTCMultiConnection.js +++ b/RTCMultiConnection/dev/RTCMultiConnection.js @@ -1,6 +1,8 @@ // RTCMultiConnection.js -function RTCMultiConnection(roomid) { +function RTCMultiConnection(roomid, forceOptions) { + forceOptions = forceOptions || {}; + var connection = this; connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + ''; @@ -8,7 +10,9 @@ function RTCMultiConnection(roomid) { var mPeer = new MultiPeers(connection); mPeer.onGettingLocalMedia = function(stream) { - setStreamEndHandler(stream); + stream.type = 'local'; + + connection.setStreamEndHandler(stream); getRMCMediaElement(stream, function(mediaElement) { mediaElement.id = stream.streamid; @@ -38,10 +42,14 @@ function RTCMultiConnection(roomid) { setMuteHandlers(connection, connection.streamEvents[stream.streamid]); connection.onstream(connection.streamEvents[stream.streamid]); - }); + }, connection); }; mPeer.onGettingRemoteMedia = function(stream, remoteUserId) { + stream.type = 'remote'; + + connection.setStreamEndHandler(stream, 'remote-stream'); + getRMCMediaElement(stream, function(mediaElement) { mediaElement.id = stream.streamid; @@ -62,7 +70,7 @@ function RTCMultiConnection(roomid) { setMuteHandlers(connection, connection.streamEvents[stream.streamid]); connection.onstream(connection.streamEvents[stream.streamid]); - }); + }, connection); }; mPeer.onRemovingRemoteMedia = function(stream, remoteUserId) { @@ -94,35 +102,16 @@ function RTCMultiConnection(roomid) { }; function onUserLeft(remoteUserId) { - if (connection.peers[remoteUserId] && connection.peers[remoteUserId].peer) { - connection.peers[remoteUserId].streams.forEach(function(stream) { - stream.stop(); - }); - - connection.peers[remoteUserId].peer.close(); - connection.peers[remoteUserId].peer = null; - - connection.onleave({ - userid: remoteUserId, - extra: connection.peers[remoteUserId].extra - }); - } - - delete connection.peers[remoteUserId]; + connection.deletePeer(remoteUserId); } + mPeer.onUserLeft = onUserLeft; mPeer.disconnectWith = function(remoteUserId, callback) { if (socket) { socket.emit('disconnect-with', remoteUserId, callback || function() {}); } - if (connection.peers[remoteUserId]) { - if (connection.peers[remoteUserId].peer) { - connection.peers[remoteUserId].peer.close(); - } - - delete connection.peers[remoteUserId]; - } + connection.deletePeer(remoteUserId); }; connection.broadcasters = []; @@ -253,8 +242,52 @@ function RTCMultiConnection(roomid) { socket.emit('become-a-public-moderator'); }; + connection.dontMakeMeModerator = function() { + socket.emit('dont-make-me-moderator'); + }; + + connection.deletePeer = function(remoteUserId) { + if (!remoteUserId) { + return; + } + + connection.onleave({ + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} + }); + + if (!!connection.peers[remoteUserId]) { + connection.peers[remoteUserId].streams.forEach(function(stream) { + stream.stop(); + }); + + var peer = connection.peers[remoteUserId].peer; + if (peer && peer.iceConnectionState !== 'closed') { + try { + peer.close(); + } catch (e) {} + } + + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].peer = null; + delete connection.peers[remoteUserId]; + } + } + + if (connection.broadcasters.indexOf(remoteUserId) !== -1) { + var newArray = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== remoteUserId) { + newArray.push(broadcaster); + } + }); + connection.broadcasters = newArray; + keepNextBroadcasterOnServer(); + } + } + connection.rejoin = function(connectionDescription) { - if (connection.isInitiator) { + if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) { return; } @@ -262,10 +295,7 @@ function RTCMultiConnection(roomid) { if (connection.peers[connectionDescription.remoteUserId]) { extra = connection.peers[connectionDescription.remoteUserId].extra; - if (connection.peers[connectionDescription.remoteUserId].peer) { - connection.peers[connectionDescription.remoteUserId].peer = null; - } - delete connection.peers[connectionDescription.remoteUserId]; + connection.deletePeer(connectionDescription.remoteUserId); } if (connectionDescription && connectionDescription.remoteUserId) { @@ -355,16 +385,10 @@ function RTCMultiConnection(roomid) { connection.peers.getAllParticipants(remoteUserId || connection.sessionid).forEach(function(participant) { mPeer.onNegotiationNeeded('dropPeerConnection', participant); - - connection.peers[participant].peer.close(); - connection.peers[participant].peer = null; - delete connection.peers[participant]; + connection.deletePeer(participant); }); connection.attachStreams.forEach(function(stream) { - stream.addEventListener('ended', function() { - connection.renegotiate(remoteUserId || connection.sessionid); - }, false); stream.stop(); }); }; @@ -381,7 +405,7 @@ function RTCMultiConnection(roomid) { if (session.audio || session.video || session.screen) { if (session.screen) { - getScreenConstraints(function(error, screen_constraints) { + connection.getScreenConstraints(function(error, screen_constraints) { if (error) { throw error; } @@ -425,8 +449,15 @@ function RTCMultiConnection(roomid) { callback(stream); } }, - onLocalMediaError: function(error) { - mPeer.onLocalMediaError(error); + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, getUserMedia_callback); + return; + } + if (callback) { callback(); } @@ -439,41 +470,30 @@ function RTCMultiConnection(roomid) { } }; - function beforeUnload(shiftModerationControlOnLeave) { + function beforeUnload(shiftModerationControlOnLeave, dontCloseSocket) { if (!connection.closeBeforeUnload) { return; } + + if (connection.isInitiator === true) { + connection.dontMakeMeModerator(); + } + connection.peers.getAllParticipants().forEach(function(participant) { mPeer.onNegotiationNeeded({ - userLeft: true, - autoCloseEntireSession: !!connection.autoCloseEntireSession + userLeft: true }, participant); if (connection.peers[participant] && connection.peers[participant].peer) { connection.peers[participant].peer.close(); } - }); - - if (socket) { - if (typeof socket.disconnect !== 'undefined') { - connection.autoReDialOnFailure = false; // Prevent reconnection - socket.disconnect(); - } - socket = null; - } - - // equivalent of connection.isInitiator - if (!connection.broadcasters.length || !!connection.autoCloseEntireSession) return; - var firstBroadcaster = connection.broadcasters[0]; - var otherBroadcasters = []; - connection.broadcasters.forEach(function(broadcaster) { - if (broadcaster !== firstBroadcaster) { - otherBroadcasters.push(broadcaster); - } + delete connection.peers[participant]; }); - connection.shiftModerationControl(firstBroadcaster, otherBroadcasters, typeof shiftModerationControlOnLeave != 'undefined' ? shiftModerationControlOnLeave : true); + if (!dontCloseSocket) { + connection.closeSocket(); + } connection.broadcasters = []; connection.isInitiator = false; @@ -483,17 +503,14 @@ function RTCMultiConnection(roomid) { window.addEventListener('beforeunload', beforeUnload, false); connection.userid = getRandomString(); - connection.changeUserId = function(newUserId) { + connection.changeUserId = function(newUserId, callback) { connection.userid = newUserId || getRandomString(); - socket.emit('changed-uuid', connection.userid); + socket.emit('changed-uuid', connection.userid, callback || function() {}); }; connection.extra = {}; - if (Object.observe) { - Object.observe(connection.extra, function(changes) { - socket.emit('extra-data-updated', connection.extra); - }); - } + connection.attachStreams = []; + connection.removeStreams = []; connection.session = { audio: true, @@ -502,52 +519,156 @@ function RTCMultiConnection(roomid) { connection.enableFileSharing = false; + // all values in kbps + connection.bandwidth = { + screen: 512, + audio: 128, + video: 512 + }; + + connection.codecs = { + audio: 'opus', + video: 'VP9' + }; + + connection.processSdp = function(sdp) { + if (isMobileDevice || isFirefox) { + return sdp; + } + + sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); + sdp = CodecsHandler.setVideoBitrates(sdp, { + min: connection.bandwidth.video * 8 * 1024, + max: connection.bandwidth.video * 8 * 1024 + }); + sdp = CodecsHandler.setOpusAttributes(sdp, { + maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, + maxplaybackrate: connection.bandwidth.audio * 8 * 1024, + stereo: 1, + maxptime: 3 + }); + + if (connection.codecs.video === 'VP9') { + sdp = CodecsHandler.preferVP9(sdp); + } + + if (connection.codecs.video === 'H264') { + sdp = CodecsHandler.removeVPX(sdp); + } + + if (connection.codecs.audio === 'G722') { + sdp = CodecsHandler.removeNonG722(sdp); + } + + return sdp; + }; + + if (typeof CodecsHandler !== 'undefined') { + connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; + } + connection.mediaConstraints = { audio: { mandatory: {}, - optional: [] + optional: [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }] }, video: { mandatory: {}, - optional: [] + optional: [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }, { + googLeakyBucket: true + }, { + facingMode: 'user' + }] } }; - DetectRTC.load(function() { - // it will force RTCMultiConnection to capture last-devices - // i.e. if external microphone is attached to system, we should prefer it over built-in devices. - DetectRTC.MediaDevices.forEach(function(device) { - if (device.kind === 'audioinput') { - connection.mediaConstraints.audio = { - optional: [{ - sourceId: device.id - }], - mandatory: {} - }; + if (isFirefox) { + connection.mediaConstraints = { + audio: true, + video: true + }; + } + if (!forceOptions.useDefaultDevices && !isMobileDevice) { + DetectRTC.load(function() { + var lastAudioDevice, lastVideoDevice; + // it will force RTCMultiConnection to capture last-devices + // i.e. if external microphone is attached to system, we should prefer it over built-in devices. + DetectRTC.MediaDevices.forEach(function(device) { + if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) { + lastAudioDevice = device; + } + + if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) { + lastVideoDevice = device; + } + }); + + if (lastAudioDevice) { if (isFirefox) { + if (connection.mediaConstraints.audio !== true) { + connection.mediaConstraints.audio.deviceId = lastAudioDevice.id; + } else { + connection.mediaConstraints.audio = { + deviceId: lastAudioDevice.id + } + } + return; + } + + if (connection.mediaConstraints.audio == true) { connection.mediaConstraints.audio = { - deviceId: device.id - }; + mandatory: {}, + optional: [] + } } - } - if (device.kind === 'videoinput') { - connection.mediaConstraints.video = { - optional: [{ - sourceId: device.id - }], - mandatory: {} - }; + if (!connection.mediaConstraints.audio.optional) { + connection.mediaConstraints.audio.optional = []; + } + + var optional = [{ + sourceId: lastAudioDevice.id + }]; + + connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional); + } + if (lastVideoDevice) { if (isFirefox) { + if (connection.mediaConstraints.video !== true) { + connection.mediaConstraints.video.deviceId = lastVideoDevice.id; + } else { + connection.mediaConstraints.video = { + deviceId: lastVideoDevice.id + } + } + return; + } + + if (connection.mediaConstraints.video == true) { connection.mediaConstraints.video = { - deviceId: device.id - }; + mandatory: {}, + optional: [] + } } + + if (!connection.mediaConstraints.video.optional) { + connection.mediaConstraints.video.optional = []; + } + + var optional = [{ + sourceId: lastVideoDevice.id + }]; + + connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional); } - }) - }); + }); + } connection.sdpConstraints = { mandatory: { @@ -566,6 +687,18 @@ function RTCMultiConnection(roomid) { googImprovedWifiBwe: true }, { googScreencastMinBitrate: 300 + }, { + googIPv6: true + }, { + googDscp: true + }, { + googCpuUnderuseThreshold: 55 + }, { + googCpuOveruseThreshold: 85 + }, { + googSuspendBelowMinBitrate: true + }, { + googCpuOveruseDetection: true }], mandatory: {} }; @@ -613,12 +746,42 @@ function RTCMultiConnection(roomid) { }; connection.close = connection.disconnect = connection.leave = function() { - beforeUnload(false); + beforeUnload(false, true); + }; + + connection.closeEntireSession = function(callback) { + callback = callback || function() {}; + socket.emit('close-entire-session', function looper() { + if (connection.getAllParticipants().length) { + setTimeout(looper, 100); + return; + } + + connection.onEntireSessionClosed({ + sessionid: connection.sessionid, + userid: connection.userid, + extra: connection.extra + }); + + connection.changeUserId(null, function() { + connection.close(); + callback(); + }); + }); + }; + + connection.onEntireSessionClosed = function(event) { + if (!connection.enableLogs) return; + console.info('Entire session is closed: ', event.sessionid, event.extra); }; connection.onstream = function(e) { var parentNode = connection.videosContainer; parentNode.insertBefore(e.mediaElement, parentNode.firstChild); + e.mediaElement.play(); + setTimeout(function() { + e.mediaElement.play(); + }, 5000); }; connection.onstreamended = function(e) { @@ -634,16 +797,6 @@ function RTCMultiConnection(roomid) { }; connection.direction = 'many-to-many'; - connection.attachStreams = []; - connection.removeStreams = []; - - Array.prototype.getStreamById = function(streamid) { - var stream; - this.forEach(function(_stream) { - if (_stream.streamid == streamid) stream = _stream; - }); - return stream; - }; connection.removeStream = function(streamid) { var stream; @@ -666,15 +819,27 @@ function RTCMultiConnection(roomid) { } }; - connection.addStream = function(session) { + connection.addStream = function(session, remoteUserId) { + if (!!session.getAudioTracks) { + if (connection.attachStreams.indexOf(session) === -1) { + if (!session.streamid) { + session.streamid = session.id; + } + + connection.attachStreams.push(session); + } + connection.renegotiate(remoteUserId); + return; + } + if (isData(session)) { - connection.renegotiate(); + connection.renegotiate(remoteUserId); return; } if (!session.audio || session.video || session.screen) { if (session.screen) { - getScreenConstraints(function(error, screen_constraints) { + connection.getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } @@ -715,15 +880,22 @@ function RTCMultiConnection(roomid) { return callback(); } - connection.renegotiate(); + connection.renegotiate(remoteUserId); }, - onLocalMediaError: function(error) { - mPeer.onLocalMediaError(error); + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, callback); + return; + } + if (callback) { return callback(); } - connection.renegotiate(); + connection.renegotiate(remoteUserId); }, localMediaConstraints: localMediaConstraints || { audio: session.audio ? connection.mediaConstraints.audio : false, @@ -789,7 +961,7 @@ function RTCMultiConnection(roomid) { session = session || {}; if (!RTCPeerConnection.prototype.getSenders) { - this.addStream(session); + connection.addStream(session); return; } @@ -799,7 +971,13 @@ function RTCMultiConnection(roomid) { } if (session instanceof MediaStream) { - replaceTrack(session.getVideoTracks()[0], remoteUserId, isVideoTrack); + if (session.getVideoTracks().length) { + replaceTrack(session.getVideoTracks()[0], remoteUserId, true); + } + + if (session.getAudioTracks().length) { + replaceTrack(session.getAudioTracks()[0], remoteUserId, false); + } return; } @@ -810,7 +988,7 @@ function RTCMultiConnection(roomid) { if (!session.audio || session.video || session.screen) { if (session.screen) { - getScreenConstraints(function(error, screen_constraints) { + connection.getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } @@ -835,8 +1013,14 @@ function RTCMultiConnection(roomid) { connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); }, - onLocalMediaError: function(error) { - mPeer.onLocalMediaError(error); + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, callback); + return; + } if (callback) { callback(); @@ -850,6 +1034,28 @@ function RTCMultiConnection(roomid) { } }; + connection.resetTrack = function(remoteUsersIds, isVideoTrack) { + if (!remoteUsersIds) { + remoteUsersIds = connection.getAllParticipants(); + } + + if (typeof remoteUsersIds == 'string') { + remoteUsersIds = [remoteUsersIds]; + } + + remoteUsersIds.forEach(function(participant) { + var peer = connection.peers[participant].peer; + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) { + connection.replaceTrack(peer.lastVideoTrack, participant, true); + } + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) { + connection.replaceTrack(peer.lastAudioTrack, participant, false); + } + }); + }; + connection.renegotiate = function(remoteUserId) { if (remoteUserId) { mPeer.renegotiatePeer(remoteUserId); @@ -861,79 +1067,96 @@ function RTCMultiConnection(roomid) { }); }; - function setStreamEndHandler(stream) { + connection.setStreamEndHandler = function(stream, isRemote) { + if (!stream || !stream.addEventListener) return; + + isRemote = !!isRemote; + if (stream.alreadySetEndHandler) { return; } stream.alreadySetEndHandler = true; stream.addEventListener('ended', function() { - delete connection.attachStreams[connection.attachStreams.indexOf(stream)]; - - if (connection.removeStreams.indexOf(stream) === -1) { - connection.removeStreams.push(stream); + if (stream.idInstance) { + currentUserMediaRequest.remove(stream.idInstance); } - connection.attachStreams = removeNullEntries(connection.attachStreams); - connection.removeStreams = removeNullEntries(connection.removeStreams); + if (!isRemote) { + delete connection.attachStreams[connection.attachStreams.indexOf(stream)]; + + if (connection.removeStreams.indexOf(stream) === -1) { + connection.removeStreams.push(stream); + } + + connection.attachStreams = removeNullEntries(connection.attachStreams); + connection.removeStreams = removeNullEntries(connection.removeStreams); + } // connection.renegotiate(); - var streamEvent = connection.streamEvents[stream]; + var streamEvent = connection.streamEvents[stream.streamid]; if (!streamEvent) { streamEvent = { stream: stream, streamid: stream.streamid, - type: 'local', + type: isRemote ? 'remote' : 'local', userid: connection.userid, extra: connection.extra, mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null }; } + + if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { + return; + } + connection.onstreamended(streamEvent); delete connection.streamEvents[stream.streamid]; }, false); - } - - if (Object.observe) { - Object.observe(connection.attachStreams, function(changes) { - changes.forEach(function(change) { - if (change.type === 'add') { - setStreamEndHandler(change.object[change.name]); - } - - if (change.type === 'remove' || change.type === 'delete') { - if (connection.removeStreams.indexOf(change.object[change.name]) === -1) { - connection.removeStreams.push(change.object[change.name]); - } - } - - connection.attachStreams = removeNullEntries(connection.attachStreams); - connection.removeStreams = removeNullEntries(connection.removeStreams); - }); - }); - } + }; - connection.onMediaError = function(error) { + connection.onMediaError = function(error, constraints) { if (!!connection.enableLogs) { - console.error(error); + console.error(error, constraints); } }; connection.addNewBroadcaster = function(broadcasterId, userPreferences) { - connection.broadcasters.forEach(function(broadcaster) { - mPeer.onNegotiationNeeded({ - newParticipant: broadcasterId, - userPreferences: userPreferences || false - }, broadcaster); - }); + if (connection.broadcasters.length) { + setTimeout(function() { + mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId, userPreferences, connection.broadcasters.join('|-,-|')); + }, 10 * 1000); + } - if (!connection.session.oneway && connection.direction === 'many-to-many' && connection.broadcasters.indexOf(broadcasterId) === -1) { + if (!connection.session.oneway && !connection.session.broadcast && connection.direction === 'many-to-many' && connection.broadcasters.indexOf(broadcasterId) === -1) { connection.broadcasters.push(broadcasterId); + keepNextBroadcasterOnServer(); } }; + connection.autoCloseEntireSession = false; + + function keepNextBroadcasterOnServer() { + if (!connection.isInitiator) return; + + if (connection.session.oneway || connection.session.broadcast || connection.direction !== 'many-to-many') { + return; + } + + var firstBroadcaster = connection.broadcasters[0]; + var otherBroadcasters = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== firstBroadcaster) { + otherBroadcasters.push(broadcaster); + } + }); + + if (connection.autoCloseEntireSession) return; + connection.shiftModerationControl(firstBroadcaster, otherBroadcasters, true); + }; + connection.filesContainer = connection.videosContainer = document.body || document.documentElement; connection.isInitiator = false; @@ -942,8 +1165,6 @@ function RTCMultiConnection(roomid) { FileProgressBarHandler.handle(connection); } - connection.autoCloseEntireSession = false; - if (typeof TranslationHandler !== 'undefined') { TranslationHandler.handle(connection); } @@ -990,26 +1211,10 @@ function RTCMultiConnection(roomid) { }, remoteUserId); }; - connection.processSdp = function(sdp) { - sdp = BandwidthHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); - sdp = BandwidthHandler.setVideoBitrates(sdp, { - min: connection.bandwidth.video, - max: connection.bandwidth.video - }); - sdp = BandwidthHandler.setOpusAttributes(sdp); - return sdp; - }; - - if (typeof BandwidthHandler !== 'undefined') { - connection.BandwidthHandler = BandwidthHandler; + if (typeof StreamsHandler !== 'undefined') { + connection.StreamsHandler = StreamsHandler; } - connection.bandwidth = { - screen: 300, // 300kbps minimum - audio: 50, - video: 256 - }; - connection.onleave = function(userid) {}; connection.invokeSelectFileDialog = function(callback) { @@ -1036,11 +1241,11 @@ function RTCMultiConnection(roomid) { return; } - if (e.stream.isVideo || e.stream.isScreen) { + if (e.muteType === 'both' || e.muteType === 'video') { + e.mediaElement.src = null; e.mediaElement.pause(); - e.mediaElement.setAttribute('poster', e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'); - } - if (e.stream.isAudio) { + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + } else if (e.muteType === 'audio') { e.mediaElement.muted = true; } }; @@ -1050,11 +1255,11 @@ function RTCMultiConnection(roomid) { return; } - if (e.stream.isVideo || e.stream.isScreen) { + if (e.unmuteType === 'both' || e.unmuteType === 'video') { + e.mediaElement.poster = null; + e.mediaElement.src = URL.createObjectURL(e.stream); e.mediaElement.play(); - e.mediaElement.removeAttribute('poster'); - } - if (e.stream.isAudio) { + } else if (e.unmuteType === 'audio') { e.mediaElement.muted = false; } }; @@ -1076,8 +1281,8 @@ function RTCMultiConnection(roomid) { console.warn(remoteUserId, 'is password protected. Your max password tries exceeded the limit.'); }; - connection.getAllParticipants = function() { - return connection.peers.getAllParticipants(); + connection.getAllParticipants = function(sender) { + return connection.peers.getAllParticipants(sender); }; if (typeof StreamsHandler !== 'undefined') { @@ -1093,20 +1298,18 @@ function RTCMultiConnection(roomid) { }; } - connection.getAllVideos = function(remoteUserId) { - var videos = []; - Array.prototype.slice.call(document.querySelectorAll('video')).forEach(function(video) { - if (video.getAttribute('data-userid') === remoteUserId) { - videos.push(video); - } - }); - return videos; - } - connection.connectSocket = function(callback) { connectSocket(callback); }; + connection.closeSocket = function() { + if (!socket) return; + if (typeof socket.disconnect !== 'undefined') { + socket.disconnect(); + } + socket = null; + }; + connection.getSocket = function(callback) { if (!socket) { connectSocket(callback); @@ -1118,7 +1321,6 @@ function RTCMultiConnection(roomid) { }; connection.getRemoteStreams = mPeer.getRemoteStreams; - connection.autoReDialOnFailure = true; var skipStreams = ['selectFirst', 'selectAll', 'forEach']; @@ -1161,7 +1363,7 @@ function RTCMultiConnection(roomid) { // default value is 15k because Firefox's receiving limit is 16k! // however 64k works chrome-to-chrome - connection.chunkSize = 15 * 1000; + connection.chunkSize = 65 * 1000; connection.maxParticipantsAllowed = 1000; @@ -1196,7 +1398,7 @@ function RTCMultiConnection(roomid) { }; connection.enableScalableBroadcast = false; - connection.singleBroadcastAttendees = 3; // each broadcast should serve only 3 users + connection.maxRelayLimitPerUser = 3; // each broadcast should serve only 3 users connection.dontCaptureUserMedia = false; connection.dontAttachStream = false; @@ -1206,5 +1408,108 @@ function RTCMultiConnection(roomid) { if (connection.enableLogs) { console.info('ReConnecting with', event.userid, '...'); } + }; + + connection.beforeAddingStream = function(stream) { + return stream; + }; + + connection.beforeRemovingStream = function(stream) { + return stream; + }; + + if (typeof isChromeExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isChromeExtensionAvailable; + } + + if (typeof isFirefoxExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isFirefoxExtensionAvailable; + } + + if (typeof getChromeExtensionStatus !== 'undefined') { + connection.getChromeExtensionStatus = getChromeExtensionStatus; + } + + connection.getScreenConstraints = function(callback) { + getScreenConstraints(function(error, screen_constraints) { + if (!error) { + screen_constraints = connection.modifyScreenConstraints(screen_constraints); + callback(error, screen_constraints); + } + }); + }; + + connection.modifyScreenConstraints = function(screen_constraints) { + return screen_constraints; + }; + + connection.onPeerStateChanged = function(state) { + if (connection.enableLogs) { + if (state.iceConnectionState.search(/closed|failed/gi) !== -1) { + console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState); + } + } + }; + + connection.isOnline = true; + + listenEventHandler('online', function() { + connection.isOnline = true; + }); + + listenEventHandler('offline', function() { + connection.isOnline = false; + }); + + connection.isLowBandwidth = false; + if (navigator && navigator.connection && navigator.connection.type) { + connection.isLowBandwidth = navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g) !== -1; + if (connection.isLowBandwidth) { + connection.bandwidth = { + audio: 30, + video: 30, + screen: 30 + }; + + if (connection.mediaConstraints.audio && connection.mediaConstraints.audio.optional.length) { + var newArray = []; + connection.mediaConstraints.audio.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.audio.optional = newArray; + } + + if (connection.mediaConstraints.video && connection.mediaConstraints.video.optional.length) { + var newArray = []; + connection.mediaConstraints.video.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.video.optional = newArray; + } + } + } + + connection.getExtraData = function(remoteUserId) { + if (!remoteUserId) throw 'remoteUserId is required.'; + if (!connection.peers[remoteUserId]) return {}; + return connection.peers[remoteUserId].extra; + }; + + if (!!forceOptions.autoOpenOrJoin) { + connection.openOrJoin(connection.sessionid); } + + connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { + if (connection.enableLogs) { + console.warn('Userid already taken.', useridAlreadyTaken, 'Your new userid:', yourNewUserId); + } + + connection.join(useridAlreadyTaken); + }; + + connection.trickleIce = true; } diff --git a/RTCMultiConnection/dev/RTCPeerConnection.js b/RTCMultiConnection/dev/RTCPeerConnection.js index 6e981a10..e7797585 100755 --- a/RTCMultiConnection/dev/RTCPeerConnection.js +++ b/RTCMultiConnection/dev/RTCPeerConnection.js @@ -34,33 +34,32 @@ if (typeof mozRTCPeerConnection !== 'undefined') { RTCPeerConnection = webkitRTCPeerConnection; } else if (typeof window.RTCPeerConnection !== 'undefined') { RTCPeerConnection = window.RTCPeerConnection; -} else { - console.error('WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'); - RTCPeerConnection = window.RTCSessionDescription = window.RTCIceCandidate = function() {}; } var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; var MediaStreamTrack = window.MediaStreamTrack; -var Plugin = {}; - -function onPluginRTCInitialized(pluginRTCObject) { - Plugin = pluginRTCObject; - MediaStreamTrack = Plugin.MediaStreamTrack; - RTCPeerConnection = Plugin.RTCPeerConnection; - RTCIceCandidate = Plugin.RTCIceCandidate; - RTCSessionDescription = Plugin.RTCSessionDescription; +window.onPluginRTCInitialized = function() { + MediaStreamTrack = window.PluginRTC.MediaStreamTrack; + RTCPeerConnection = window.PluginRTC.RTCPeerConnection; + RTCIceCandidate = window.PluginRTC.RTCIceCandidate; + RTCSessionDescription = window.PluginRTC.RTCSessionDescription; } -if (typeof PluginRTC !== 'undefined') onPluginRTCInitialized(PluginRTC); -var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; -var isIE = !!document.documentMode; -var isPluginRTC = isSafari || isIE; +if (typeof window.PluginRTC !== 'undefined') { + window.onPluginRTCInitialized(); +} function PeerInitiator(config) { - this.extra = config.remoteSdp ? config.remoteSdp.extra : config.rtcMultiConnection.extra; - this.remoteUserId = config.remoteUserId; + if (!RTCPeerConnection) { + throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'; + } + + var connection = config.rtcMultiConnection; + + this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra; + this.userid = config.userid; this.streams = []; this.channels = []; this.connectionDescription = config.connectionDescription; @@ -73,27 +72,6 @@ function PeerInitiator(config) { var allRemoteStreams = {}; - if (Object.observe) { - var that = this; - Object.observe(this.channels, function(changes) { - changes.forEach(function(change) { - if (change.type === 'add') { - change.object[change.name].addEventListener('close', function() { - delete that.channels[that.channels.indexOf(change.object[change.name])]; - that.channels = removeNullEntries(that.channels); - }, false); - } - if (change.type === 'remove' || change.type === 'delete') { - if (that.channels.indexOf(change.object[change.name]) !== -1) { - delete that.channels.indexOf(change.object[change.name]); - } - } - - that.channels = removeNullEntries(that.channels); - }); - }); - } - defaults.sdpConstraints = setSdpConstraints({ OfferToReceiveAudio: true, OfferToReceiveVideo: true @@ -107,15 +85,15 @@ function PeerInitiator(config) { } var localStreams = []; - config.localStreams.forEach(function(stream) { + connection.attachStreams.forEach(function(stream) { if (!!stream) localStreams.push(stream); }); if (!renegotiatingPeer) { peer = new RTCPeerConnection(navigator.onLine ? { - iceServers: config.iceServers, + iceServers: connection.iceServers, iceTransports: 'all' - } : null, config.optionalArgument); + } : null, window.PluginRTC ? null : connection.optionalArgument); } else { peer = config.peerRef; @@ -126,9 +104,10 @@ function PeerInitiator(config) { } }); - config.removeStreams.forEach(function(streamToRemove, index) { + connection.removeStreams.forEach(function(streamToRemove, index) { if (stream === streamToRemove) { - if (!!peer.removeStream) { + stream = connection.beforeRemovingStream(stream); + if (stream && !!peer.removeStream) { peer.removeStream(stream); } @@ -142,8 +121,33 @@ function PeerInitiator(config) { }); } + if (connection.DetectRTC.browser.name === 'Firefox') { + peer.removeStream = function(stream) { + stream.mute(); + connection.StreamsHandler.onSyncNeeded(stream.streamid, 'stream-removed'); + }; + } + peer.onicecandidate = function(event) { - if (!event.candidate) return; + if (!event.candidate) { + if (!connection.trickleIce) { + var localSdp = peer.localDescription; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: that.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare, + isFirefoxOffered: isFirefox + }); + } + return; + } + + if (!connection.trickleIce) return; config.onLocalCandidate({ candidate: event.candidate.candidate, sdpMid: event.candidate.sdpMid, @@ -165,36 +169,29 @@ function PeerInitiator(config) { return; } - peer.addStream(localStream); + localStream = connection.beforeAddingStream(localStream); + if (localStream) { + peer.addStream(localStream); + } }); peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() { + var extra = that.extra; + if (connection.peers[that.userid]) { + extra = connection.peers[that.userid].extra || extra; + } + + if (!peer) { + return; + } + config.onPeerStateChanged({ iceConnectionState: peer.iceConnectionState, iceGatheringState: peer.iceGatheringState, signalingState: peer.signalingState, - extra: that.extra, - userid: that.remoteUserId + extra: extra, + userid: that.userid }); - - if (peer.iceConnectionState.search(/disconnected|closed|failed/gi) !== -1) { - if (peer.firedOnce) return; - peer.firedOnce = true; - - for (var id in allRemoteStreams) { - config.onRemoteStreamRemoved(allRemoteStreams[id]); - } - allRemoteStreams = {}; - - if (that.connectionDescription && config.rtcMultiConnection.userid == that.connectionDescription.sender && !!config.rtcMultiConnection.autoReDialOnFailure) { - setTimeout(function() { - if (peer.iceConnectionState.search(/disconnected|closed|failed/gi) !== -1) { - config.rtcMultiConnection.rejoin(that.connectionDescription); - peer.firedOnce = false; - } - }, 5000); - } - } }; var sdpConstraints = { @@ -220,11 +217,12 @@ function PeerInitiator(config) { event.stream.isVideo = streamToShare.isVideo; event.stream.isScreen = streamToShare.isScreen; } - event.stream.streamid = event.stream.id; if (!event.stream.stop) { event.stream.stop = function() { - fireEvent(event.stream, 'ended', event); + if (isFirefox) { + fireEvent(this, 'ended'); + } }; } allRemoteStreams[event.stream.id] = event.stream; @@ -246,15 +244,10 @@ function PeerInitiator(config) { }; this.addRemoteSdp = function(remoteSdp) { + remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), function() {}, function(error) { - if (!!config.rtcMultiConnection.enableLogs) { - console.error(JSON.stringify(error, null, '\t')); - } - - if (!!config.rtcMultiConnection.autoReDialOnFailure) { - setTimeout(function() { - config.rtcMultiConnection.rejoin(that.connectionDescription); - }, 2000); + if (!!connection.enableLogs) { + console.error(JSON.stringify(error, null, '\t'), '\n', remoteSdp.type, remoteSdp.sdp); } }); }; @@ -265,7 +258,7 @@ function PeerInitiator(config) { isOfferer = false; } - if (config.enableDataChannels === true) { + if (connection.session.data === true) { createDataChannel(); } @@ -322,10 +315,10 @@ function PeerInitiator(config) { peer.channel = channel; } - if (config.session.audio == 'two-way' || config.session.video == 'two-way' || config.session.screen == 'two-way') { + if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') { defaults.sdpConstraints = setSdpConstraints({ - OfferToReceiveAudio: config.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), - OfferToReceiveVideo: config.session.video == 'two-way' || config.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) + OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), + OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) }); } @@ -339,8 +332,10 @@ function PeerInitiator(config) { }); peer[isOfferer ? 'createOffer' : 'createAnswer'](function(localSdp) { - localSdp.sdp = config.processSdp(localSdp.sdp); + localSdp.sdp = connection.processSdp(localSdp.sdp); peer.setLocalDescription(localSdp); + + if (!connection.trickleIce) return; config.onLocalSdp({ type: localSdp.type, sdp: localSdp.sdp, @@ -348,28 +343,33 @@ function PeerInitiator(config) { renegotiatingPeer: !!config.renegotiatingPeer || false, connectionDescription: that.connectionDescription, dontGetRemoteStream: !!config.dontGetRemoteStream, - extra: config.rtcMultiConnection ? config.rtcMultiConnection.extra : {}, + extra: connection ? connection.extra : {}, streamsToShare: streamsToShare, isFirefoxOffered: isFirefox }); }, function(error) { - if (!!config.rtcMultiConnection.enableLogs) { + if (!!connection.enableLogs) { console.error('sdp-error', error); } - - if (!!config.rtcMultiConnection.autoReDialOnFailure && !isFirefox && !isFirefoxOffered) { - setTimeout(function() { - config.rtcMultiConnection.rejoin(that.connectionDescription); - }, 2000); - } }, defaults.sdpConstraints); peer.nativeClose = peer.close; peer.close = function() { - if (peer && peer.iceConnectionState === 'connected') { - peer.nativeClose(); - peer = null; + if (!peer) { + return; } + + try { + if (peer.iceConnectionState.search(/closed|failed/gi) === -1) { + peer.getRemoteStreams().forEach(function(stream) { + stream.stop(); + }); + } + peer.nativeClose(); + } catch (e) {} + + peer = null; + that.peer = null; }; this.peer = peer; diff --git a/RTCMultiConnection/dev/Screen-Capturing.js b/RTCMultiConnection/dev/Screen-Capturing.js index 74e925d4..7a2961a8 100755 --- a/RTCMultiConnection/dev/Screen-Capturing.js +++ b/RTCMultiConnection/dev/Screen-Capturing.js @@ -160,8 +160,10 @@ function getChromeExtensionStatus(extensionid, callback) { // this function explains how to use above methods/objects function getScreenConstraints(callback) { var firefoxScreenConstraints = { - mozMediaSource: 'screen', - mediaSource: 'screen' + mozMediaSource: 'window', + mediaSource: 'window', + width: 29999, + height: 8640 }; if (isFirefox) return callback(null, firefoxScreenConstraints); @@ -173,13 +175,12 @@ function getScreenConstraints(callback) { var screen_constraints = { mandatory: { chromeMediaSource: chromeMediaSource, - maxWidth: screen.width > 1920 ? screen.width : 1920, - maxHeight: screen.height > 1080 ? screen.height : 1080, + maxWidth: 29999, + maxHeight: 8640, minFrameRate: 30, - maxFrameRate: 64, - minAspectRatio: 1.77, - googLeakyBucket: true, - googTemporalLayeredScreencast: true + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true }, optional: [] }; diff --git a/RTCMultiConnection/dev/SocketConnection.js b/RTCMultiConnection/dev/SocketConnection.js index 0049330d..00cb18d7 100755 --- a/RTCMultiConnection/dev/SocketConnection.js +++ b/RTCMultiConnection/dev/SocketConnection.js @@ -7,10 +7,16 @@ function SocketConnection(connection, connectCallback) { if (connection.enableScalableBroadcast) { parameters += '&enableScalableBroadcast=true'; - parameters += '&singleBroadcastAttendees=' + connection.singleBroadcastAttendees; + parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2); } - var socket = io.connect((connection.socketURL || '/') + parameters, connection.socketOptions); + var socket; + + try { + socket = io((connection.socketURL || '/') + parameters); + } catch (e) { + socket = io.connect((connection.socketURL || '/') + parameters, connection.socketOptions); + } var mPeer = connection.multiPeersHandler; @@ -43,7 +49,7 @@ function SocketConnection(connection, connectCallback) { var action = message.message.action; - if (action === 'ended') { + if (action === 'ended' || action === 'stream-removed') { connection.onstreamended(stream); return; } @@ -59,7 +65,7 @@ function SocketConnection(connection, connectCallback) { } mPeer.onNegotiationNeeded({ - allParticipants: connection.peers.getAllParticipants(message.sender) + allParticipants: connection.getAllParticipants(message.sender) }, message.sender); return; } @@ -73,11 +79,7 @@ function SocketConnection(connection, connectCallback) { } if (message.message === 'dropPeerConnection') { - if (connection.peers[message.sender]) { - connection.peers[message.sender].peer.close(); - connection.peers[message.sender].peer = null; - delete connection.peers[message.sender]; - } + connection.deletePeer(message.sender); return; } @@ -128,11 +130,7 @@ function SocketConnection(connection, connectCallback) { if (message.message.newParticipationRequest && message.sender !== connection.userid) { if (connection.peers[message.sender]) { - if (connection.peers[message.sender].peer) { - connection.peers[message.sender].peer.close(); - connection.peers[message.sender].peer = null; - } - delete connection.peers[message.sender]; + connection.deletePeer(message.sender); } var userPreferences = { @@ -246,10 +244,7 @@ function SocketConnection(connection, connectCallback) { extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {} }); - if (connection.peers[remoteUserId] && connection.peers[remoteUserId].peer) { - connection.peers[remoteUserId].peer.close(); - delete connection.peers[remoteUserId]; - } + connection.deletePeer(remoteUserId); }); socket.on('user-connected', function(userid) { @@ -264,6 +259,22 @@ function SocketConnection(connection, connectCallback) { }); }); + socket.on('closed-entire-session', function(sessionid, extra) { + connection.leave(); + connection.onEntireSessionClosed({ + sessionid: sessionid, + userid: sessionid, + extra: extra + }); + }); + + socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) { + connection.isInitiator = false; + connection.userid = yourNewUserId; + + connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId); + }) + socket.on('logs', function(log) { if (!connection.enableLogs) return; console.debug('server-logs', log); diff --git a/RTCMultiConnection/dev/StreamsHandler.js b/RTCMultiConnection/dev/StreamsHandler.js index b828bb1f..b0dd7e9c 100755 --- a/RTCMultiConnection/dev/StreamsHandler.js +++ b/RTCMultiConnection/dev/StreamsHandler.js @@ -26,9 +26,11 @@ var StreamsHandler = (function() { } function setHandlers(stream, syncAction, connection) { + if (!stream || !stream.addEventListener) return; + if (typeof syncAction == 'undefined' || syncAction == true) { stream.addEventListener('ended', function() { - StreamsHandler.onSyncNeeded(stream.streamid, 'ended'); + StreamsHandler.onSyncNeeded(this.streamid, 'ended'); }, false); } @@ -56,7 +58,7 @@ var StreamsHandler = (function() { StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type); } - connection.streamEvents[stream.streamid].muteType = type; + connection.streamEvents[stream.streamid].muteType = type || 'both'; fireEvent(stream, 'mute', type); }; @@ -107,7 +109,7 @@ var StreamsHandler = (function() { StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type); } - connection.streamEvents[stream.streamid].unmuteType = type; + connection.streamEvents[stream.streamid].unmuteType = type || 'both'; fireEvent(stream, 'unmute', type); }; diff --git a/RTCMultiConnection/dev/getScreenId.js b/RTCMultiConnection/dev/getScreenId.js index 84df1cce..031d2cc5 100755 --- a/RTCMultiConnection/dev/getScreenId.js +++ b/RTCMultiConnection/dev/getScreenId.js @@ -30,7 +30,9 @@ getScreenId(function (error, sourceId, screen_constraints) { callback(null, 'firefox', { video: { mozMediaSource: 'window', - mediaSource: 'window' + mediaSource: 'window', + width: 29999, + height: 8640 } }); return; @@ -64,8 +66,12 @@ getScreenId(function (error, sourceId, screen_constraints) { video: { mandatory: { chromeMediaSource: error ? 'screen' : 'desktop', - maxWidth: window.screen.width > 1920 ? window.screen.width : 1920, - maxHeight: window.screen.height > 1080 ? window.screen.height : 1080 + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true }, optional: [] } @@ -136,7 +142,9 @@ getScreenId(function (error, sourceId, screen_constraints) { callback(null, 'firefox', { video: { mozMediaSource: 'window', - mediaSource: 'window' + mediaSource: 'window', + width: 29999, + height: 8640 } }); return; @@ -170,8 +178,12 @@ getScreenId(function (error, sourceId, screen_constraints) { video: { mandatory: { chromeMediaSource: error ? 'screen' : 'desktop', - maxWidth: window.screen.width > 1920 ? window.screen.width : 1920, - maxHeight: window.screen.height > 1080 ? window.screen.height : 1080 + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true }, optional: [] } diff --git a/RTCMultiConnection/dev/getUserMedia.js b/RTCMultiConnection/dev/getUserMedia.js index 07afd5b7..ff68ed1f 100755 --- a/RTCMultiConnection/dev/getUserMedia.js +++ b/RTCMultiConnection/dev/getUserMedia.js @@ -1,5 +1,9 @@ // getUserMediaHandler.js +if (typeof webrtcUtils !== 'undefined') { + webrtcUtils.enableLogs = false; +} + function setStreamType(constraints, stream) { if (constraints.mandatory && constraints.mandatory.chromeMediaSource) { stream.isScreen = true; @@ -11,10 +15,31 @@ function setStreamType(constraints, stream) { stream.isAudio = true; } } + var currentUserMediaRequest = { streams: [], mutex: false, - queueRequests: [] + queueRequests: [], + remove: function(idInstance) { + this.mutex = false; + + var stream = this.streams[idInstance]; + if (!stream) { + return; + } + + stream = stream.stream; + + var options = stream.currentUserMediaRequestOptions; + + if (this.queueRequests.indexOf(options)) { + delete this.queueRequests[this.queueRequests.indexOf(options)]; + this.queueRequests = removeNullEntries(this.queueRequests); + } + + this.streams[idInstance].stream = null; + delete this.streams[idInstance]; + } }; function getUserMediaHandler(options) { @@ -54,9 +79,9 @@ function getUserMediaHandler(options) { if (currentUserMediaRequest.streams[idInstance]) { streaming(currentUserMediaRequest.streams[idInstance].stream, true); } else { - if (isPluginRTC) { + if (isPluginRTC && window.PluginRTC) { var mediaElement = document.createElement('video'); - Plugin.getUserMedia({ + window.PluginRTC.getUserMedia({ audio: true, video: true }, function(stream) { @@ -67,22 +92,11 @@ function getUserMediaHandler(options) { return; } - navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - - if (typeof DetectRTC !== 'undefined') { - if (!DetectRTC.hasMicrophone) { - options.localMediaConstraints.audio = false; - } - - if (!DetectRTC.hasWebcam) { - options.localMediaConstraints.video = false; - } - } - - navigator.getMedia(options.localMediaConstraints, function(stream) { - stream.streamid = stream.id || getRandomString(); + navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream) { + stream.streamid = stream.streamid || stream.id || getRandomString(); + stream.idInstance = idInstance; streaming(stream); - }, function(error) { + }).catch(function(error) { options.onLocalMediaError(error, options.localMediaConstraints); }); } diff --git a/RTCMultiConnection/dev/globals.js b/RTCMultiConnection/dev/globals.js index 492adbb1..4f126f56 100755 --- a/RTCMultiConnection/dev/globals.js +++ b/RTCMultiConnection/dev/globals.js @@ -6,14 +6,27 @@ var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Const var isChrome = !!window.chrome && !isOpera; var isIE = !!document.documentMode; -var isPluginRTC = isSafari || isIE; - var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); +if (typeof cordova !== 'undefined') { + isMobileDevice = true; + isChrome = true; +} + +if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + isChrome = true; +} + +var isPluginRTC = !isMobileDevice && (isSafari || isIE); + +if (isPluginRTC && typeof URL !== 'undefined') { + URL.createObjectURL = function() {}; +} + // detect node-webkit var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); - var chromeVersion = 50; var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); if (isChrome && matchArray && matchArray[2]) { @@ -73,6 +86,8 @@ function setHarkEvents(connection, streamEvent) { } function setMuteHandlers(connection, streamEvent) { + if (!streamEvent.stream || !streamEvent.stream.addEventListener) return; + streamEvent.stream.addEventListener('mute', function(event) { event = connection.streamEvents[event.target.streamid]; @@ -111,19 +126,19 @@ function getRandomString() { // Get HTMLAudioElement/HTMLVideoElement accordingly -function getRMCMediaElement(stream, callback) { +function getRMCMediaElement(stream, callback, connection) { var isAudioOnly = false; - if (!stream.getVideoTracks().length) { + if (!!stream.getVideoTracks && !stream.getVideoTracks().length) { isAudioOnly = true; } var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video'); - if (isPluginRTC) { + if (isPluginRTC && window.PluginRTC) { connection.videosContainer.insertBefore(mediaElement, connection.videosContainer.firstChild); setTimeout(function() { - Plugin.attachMediaStream(mediaElement, stream); + window.PluginRTC.attachMediaStream(mediaElement, stream); callback(mediaElement); }, 1000); @@ -132,17 +147,42 @@ function getRMCMediaElement(stream, callback) { // "mozSrcObject" is always preferred over "src"!! mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.URL.createObjectURL(stream); - mediaElement.controls = true; // http://goo.gl/WZ5nFl // Firefox don't yet support onended for any stream (remote/local) if (isFirefox) { mediaElement.addEventListener('ended', function() { - if (stream.onended) { - stream.onended(); + // fireEvent(stream, 'ended', stream); + currentUserMediaRequest.remove(stream.idInstance); + + if (stream.type === 'local') { + StreamsHandler.onSyncNeeded(stream.streamid, 'ended'); + + connection.attachStreams.forEach(function(aStream, idx) { + if (stream.streamid === aStream.streamid) { + delete connection.attachStreams[idx]; + } + }); + + var newStreamsArray = []; + connection.attachStreams.forEach(function(aStream) { + if (aStream) { + newStreamsArray.push(aStream); + } + }); + connection.attachStreams = newStreamsArray; + + var streamEvent = connection.streamEvents[stream.streamid]; + + if (streamEvent) { + connection.onstreamended(streamEvent); + return; + } + if (this.parentNode) { + this.parentNode.removeChild(this); + } } - fireEvent(stream, 'ended', stream.type); }, false); } @@ -199,16 +239,50 @@ if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefine } /*global MediaStream:true */ -if (typeof MediaStream !== 'undefined' && !('stop' in MediaStream.prototype)) { - MediaStream.prototype.stop = function() { - this.getAudioTracks().forEach(function(track) { - track.stop(); - }); +if (typeof MediaStream !== 'undefined') { + if (!('getVideoTracks' in MediaStream.prototype)) { + MediaStream.prototype.getVideoTracks = function() { + if (!this.getTracks) { + return []; + } - this.getVideoTracks().forEach(function(track) { - track.stop(); - }); + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('video') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; - fireEvent(this, 'ended'); - }; + MediaStream.prototype.getAudioTracks = function() { + if (!this.getTracks) { + return []; + } + + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('audio') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; + } + + if (!('stop' in MediaStream.prototype)) { + MediaStream.prototype.stop = function() { + this.getAudioTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + + this.getVideoTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + }; + } } diff --git a/RTCMultiConnection/dev/gumadapter.js b/RTCMultiConnection/dev/gumadapter.js new file mode 100644 index 00000000..18b3629d --- /dev/null +++ b/RTCMultiConnection/dev/gumadapter.js @@ -0,0 +1,367 @@ +// Last time updated at Fri Jan 08 2016 14:06 + +// gumadapter.js +// https://cdn.webrtc-experiment.com/gumadapter.js + +// getUserMedia hacks from git/webrtc/adapter; +// removed redundant codes +// A-to-Zee, all copyrights goes to: +// https://github.com/webrtc/adapter/blob/master/LICENSE.md + +var getUserMedia = null; +var webrtcDetectedBrowser = null; +var webrtcDetectedVersion = null; +var webrtcMinimumVersion = null; + +var webrtcUtils = window.webrtcUtils || {}; +if (!webrtcUtils.enableLogs) { + webrtcUtils.enableLogs = true; +} +if (!webrtcUtils.log) { + webrtcUtils.log = function() { + if (!webrtcUtils.enableLogs) { + return; + } + + // 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 (!webrtcUtils.extractVersion) { + webrtcUtils.extractVersion = function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }; +} + +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 = stream ? URL.createObjectURL(stream) : null; + } + } + }); + } + + // chrome 50+ supports promises over "play" method + HTMLMediaElement.prototype.nativePlay = HTMLMediaElement.prototype.play; + HTMLMediaElement.prototype.play = function() { + var myself = this; + var promise = myself.nativePlay(); + if (promise) { + promise.then(function() { + // maybe it is Android + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }).catch(function() { + // maybe it is iOS webview + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }); + } + }; + + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; +} + +if (typeof window === 'undefined' || !window.navigator) { + webrtcDetectedBrowser = 'not a browser'; +} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcDetectedBrowser = 'firefox'; + + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); + + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; + + // 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().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + +} else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcDetectedBrowser = 'chrome'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; + + // 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 + }; + } + + // 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.'); + }; + } + +} else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); + + // 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); + }); +} + +if (typeof module !== 'undefined') { + module.exports = { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; +} else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; + }); +} diff --git a/RTCMultiConnection/dev/ios-hacks.js b/RTCMultiConnection/dev/ios-hacks.js new file mode 100644 index 00000000..5b426064 --- /dev/null +++ b/RTCMultiConnection/dev/ios-hacks.js @@ -0,0 +1,20 @@ +// ios-hacks.js + +function setCordovaAPIs() { + if (DetectRTC.osName !== 'iOS') return; + if (typeof cordova === 'undefined' || typeof cordova.plugins === 'undefined' || typeof cordova.plugins.iosrtc === 'undefined') return; + + var iosrtc = cordova.plugins.iosrtc; + window.webkitRTCPeerConnection = iosrtc.RTCPeerConnection; + window.RTCSessionDescription = iosrtc.RTCSessionDescription; + window.RTCIceCandidate = iosrtc.RTCIceCandidate; + window.MediaStream = iosrtc.MediaStream; + window.MediaStreamTrack = iosrtc.MediaStreamTrack; + navigator.getUserMedia = navigator.webkitGetUserMedia = iosrtc.getUserMedia; + + iosrtc.debug.enable('iosrtc*'); + iosrtc.registerGlobals(); +} + +document.addEventListener('deviceready', setCordovaAPIs, false); +setCordovaAPIs(); diff --git a/RTCMultiConnection/dist/README.md b/RTCMultiConnection/dist/README.md new file mode 100755 index 00000000..e309a9bf --- /dev/null +++ b/RTCMultiConnection/dist/README.md @@ -0,0 +1,30 @@ +## Link Script Files + +All files from `/dist` directory are available on CDN: `https://cdn.webrtc-experiment.com:443/` + +```html + + + + + + + + + + +``` + +If you're sharing files, you also need to link: + +```html + + + + + + + +``` + +> You can link multiple files from `dev` directory. Order doesn't matters. diff --git a/Chrome-Extensions/file-sharing/FileBufferReader.js b/RTCMultiConnection/dist/rmc3.fbr.js old mode 100755 new mode 100644 similarity index 93% rename from Chrome-Extensions/file-sharing/FileBufferReader.js rename to RTCMultiConnection/dist/rmc3.fbr.js index e0e5805e..5401695e --- a/Chrome-Extensions/file-sharing/FileBufferReader.js +++ b/RTCMultiConnection/dist/rmc3.fbr.js @@ -9,12 +9,37 @@ function FileBufferReader() { fbr.users = {}; fbr.readAsArrayBuffer = function(file, earlyCallback, extra) { + if (!file.slice) { + console.warn('Not a real File object.', file); + return; + } + + extra = extra || { + userid: 0 + }; + + if (file.extra) { + if (typeof file.extra === 'string') { + extra.extra = file.extra; + } else { + for (var e in file.extra) { + extra[e] = file.extra[e]; + } + } + } + + extra.fileName = file.name; + + if (file.uuid) { + extra.fileUniqueId = file.uuid; + } + var options = { + uuid: file.uuid || 0, file: file, earlyCallback: earlyCallback, - extra: extra || { - userid: 0 - } + extra: extra, + chunkSize: extra.chunkSize }; fbrHelper.readAsArrayBuffer(fbr, options); @@ -127,7 +152,7 @@ function FileBufferReaderHelper() { type: 'application/javascript' })); - if(!window.fileBufferWorker) { + if (!window.fileBufferWorker) { window.fileBufferWorker = new Worker(blob); } @@ -145,14 +170,6 @@ function FileBufferReaderHelper() { }; } - if (chunk.end) { - for (var f in options.file) { - if (![chunk[f]]) { - chunk[f] = options.file[f]; - } - } - } - options.extra = options.extra || { userid: 0 }; @@ -172,7 +189,13 @@ function FileBufferReaderHelper() { earlyCallback = null; } } - if (typeof Worker !== 'undefined') { + + if (!!navigator.mozGetUserMedia) { + window.___Worker = window.Worker; + delete window.Worker; + } + + if (!!window.Worker && typeof Worker === 'function') { var webWorker = processInWebWorker(fileReaderWrapper); webWorker.onmessage = function(event) { @@ -181,13 +204,11 @@ function FileBufferReaderHelper() { webWorker.postMessage(options); } else { - var reader = new FileReader(); - reader.readAsDataURL(options); - reader.onload = function(event) { - callback(event.target.result); - }; + fileReaderWrapper(options, processChunk); - fileReaderWrapper(option, processChunk); + if (!!navigator.mozGetUserMedia) { + window.Worker = window.___Worker; + } } }; @@ -198,7 +219,7 @@ function FileBufferReaderHelper() { var file = options.file; if (!file.uuid) { - file.uuid = (Math.random() * 100).toString().replace(/\./g, ''); + file.uuid = options.fileUniqueId || (Math.random() * 100).toString().replace(/\./g, ''); } var chunkSize = options.chunkSize || 15 * 1000; @@ -222,10 +243,12 @@ function FileBufferReaderHelper() { uuid: file.uuid, maxChunks: maxChunks, size: file.size, - name: file.name, + name: file.name || options.extra.fileName, type: file.type, lastModifiedDate: !!file.lastModifiedDate ? file.lastModifiedDate.toString() : '', - start: true + start: true, + extra: options.extra || options, + url: URL.createObjectURL(file) }); var blob, reader = new FileReader(); @@ -246,11 +269,12 @@ function FileBufferReaderHelper() { uuid: file.uuid, maxChunks: maxChunks, size: file.size, - name: file.name, + name: file.name || options.extra.fileName, lastModifiedDate: !!file.lastModifiedDate ? file.lastModifiedDate.toString() : '', url: URL.createObjectURL(file), type: file.type, - end: true + end: true, + extra: options.extra || options }); } }); @@ -275,9 +299,10 @@ function FileBufferReaderHelper() { maxChunks: maxChunks, size: file.size, - name: file.name, + name: file.name || options.extra.fileName, lastModifiedDate: !!file.lastModifiedDate ? file.lastModifiedDate.toString() : '', - type: file.type + type: file.type, + extra: options.extra || options }); currentPosition++; @@ -348,6 +373,7 @@ window.FileSelector = function() { function FileBufferReceiver(fbr) { var packets = {}; + var missedChunks = []; function receive(chunk, callback) { if (!chunk.uuid) { @@ -359,11 +385,34 @@ function FileBufferReceiver(fbr) { if (chunk.start && !packets[chunk.uuid]) { packets[chunk.uuid] = []; + + if (!!missedChunks[chunk.uuid]) { + packets[chunk.uuid].push(chunk.buffer); + + // need to order "missedChunks" here + missedChunks[chunk.uuid].forEach(function(chunk) { + receive(chunk, callback); + }); + + delete missedChunks[chunk.uuid]; + } + if (fbr.onBegin) fbr.onBegin(chunk); } if (!chunk.end && chunk.buffer) { - packets[chunk.uuid].push(chunk.buffer); + if (!packets[chunk.uuid]) { + // seems {start:true} is skipped or lost or unordered. + if (!missedChunks[chunk.uuid]) { + missedChunks[chunk.uuid] = []; + } + missedChunks[chunk.uuid].push(chunk); + return; + } + + if (packets[chunk.uuid].indexOf(chunk.buffer) == -1) { + packets[chunk.uuid].push(chunk.buffer); + } } if (chunk.end) { @@ -382,7 +431,8 @@ function FileBufferReceiver(fbr) { }); blob = merge(blob, chunk); blob.url = URL.createObjectURL(blob); - blob.uuid = chunk.uuid; + blob.uuid = chunk.uuid || blob.extra.fileUniqueId; + blob.name = blob.name || blob.extra.fileName; if (!blob.size) console.error('Something went wrong. Blob Size is 0.'); diff --git a/RTCMultiConnection/dist/rmc3.fbr.min.js b/RTCMultiConnection/dist/rmc3.fbr.min.js new file mode 100644 index 00000000..f00835e3 --- /dev/null +++ b/RTCMultiConnection/dist/rmc3.fbr.min.js @@ -0,0 +1,3 @@ +// Last time updated: 2016-03-17 11:15:55 AM UTC + +function FileBufferReader(){function fbrClone(from,to){if(null==from||"object"!=typeof from)return from;if(from.constructor!=Object&&from.constructor!=Array)return from;if(from.constructor==Date||from.constructor==RegExp||from.constructor==Function||from.constructor==String||from.constructor==Number||from.constructor==Boolean)return new from.constructor(from);to=to||new from.constructor;for(var name in from)to[name]="undefined"==typeof to[name]?fbrClone(from[name],null):to[name];return to}var fbr=this,fbrHelper=new FileBufferReaderHelper;fbr.chunks={},fbr.users={},fbr.readAsArrayBuffer=function(file,earlyCallback,extra){if(!file.slice)return void console.warn("Not a real File object.",file);if(extra=extra||{userid:0},file.extra)if("string"==typeof file.extra)extra.extra=file.extra;else for(var e in file.extra)extra[e]=file.extra[e];extra.fileName=file.name,file.uuid&&(extra.fileUniqueId=file.uuid);var options={uuid:file.uuid||0,file:file,earlyCallback:earlyCallback,extra:extra,chunkSize:extra.chunkSize};fbrHelper.readAsArrayBuffer(fbr,options)},fbr.getNextChunk=function(fileUUID,callback,userid){var allFileChunks=fbr.chunks[fileUUID];if(allFileChunks){var currentPosition;"undefined"!=typeof userid?(fbr.users[userid+""]||(fbr.users[userid+""]={fileUUID:fileUUID,userid:userid,currentPosition:-1}),fbr.users[userid+""].currentPosition++,currentPosition=fbr.users[userid+""].currentPosition):(fbr.chunks[fileUUID].currentPosition++,currentPosition=fbr.chunks[fileUUID].currentPosition);var nextChunk=allFileChunks[currentPosition];nextChunk&&(nextChunk=fbrClone(nextChunk),"undefined"!=typeof userid&&(nextChunk.remoteUserId=userid+""),nextChunk.start&&fbr.onBegin(nextChunk),nextChunk.end&&fbr.onEnd(nextChunk),fbr.onProgress(nextChunk),fbr.convertToArrayBuffer(nextChunk,function(buffer){return nextChunk.currentPosition==nextChunk.maxChunks?void callback(buffer,!0):void callback(buffer,!1)}))}};var fbReceiver=new FileBufferReceiver(fbr);fbr.addChunk=function(chunk,callback){return chunk?void fbReceiver.receive(chunk,function(uuid){fbr.convertToArrayBuffer({readyForNextChunk:!0,uuid:uuid},callback)}):void console.error("Chunk is missing.")},fbr.onBegin=function(){},fbr.onEnd=function(){},fbr.onProgress=function(){},fbr.convertToObject=FileConverter.ConvertToObject,fbr.convertToArrayBuffer=FileConverter.ConvertToArrayBuffer,fbr.setMultipleUsers=function(){}}function FileBufferReaderHelper(){function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"}));return window.fileBufferWorker||(window.fileBufferWorker=new Worker(blob)),window.fileBufferWorker}function fileReaderWrapper(options,callback){function addChunks(fileName,binarySlice,addChunkCallback){numOfChunksInSlice=Math.ceil(binarySlice.byteLength/chunkSize);for(var i=0;numOfChunksInSlice>i;i++){var start=i*chunkSize;chunks[currentPosition]=binarySlice.slice(start,Math.min(start+chunkSize,binarySlice.byteLength)),callback({uuid:file.uuid,buffer:chunks[currentPosition],currentPosition:currentPosition,maxChunks:maxChunks,size:file.size,name:file.name||options.extra.fileName,lastModifiedDate:file.lastModifiedDate?file.lastModifiedDate.toString():"",type:file.type,extra:options.extra||options}),currentPosition++}currentPosition==maxChunks&&(hasEntireFile=!0),addChunkCallback()}callback=callback||function(chunk){postMessage(chunk)};var file=options.file;file.uuid||(file.uuid=options.fileUniqueId||(100*Math.random()).toString().replace(/\./g,""));var chunkSize=options.chunkSize||15e3,sliceId=0,cacheSize=chunkSize,chunksPerSlice=Math.floor(Math.min(1e8,cacheSize)/chunkSize),sliceSize=chunksPerSlice*chunkSize,maxChunks=Math.ceil(file.size/chunkSize);file.maxChunks=maxChunks;var numOfChunksInSlice,hasEntireFile,currentPosition=0,chunks=[];callback({currentPosition:currentPosition,uuid:file.uuid,maxChunks:maxChunks,size:file.size,name:file.name||options.extra.fileName,type:file.type,lastModifiedDate:file.lastModifiedDate?file.lastModifiedDate.toString():"",start:!0,extra:options.extra||options,url:URL.createObjectURL(file)});var blob,reader=new FileReader;reader.onloadend=function(evt){evt.target.readyState==FileReader.DONE&&addChunks(file.name,evt.target.result,function(){sliceId++,(sliceId+1)*sliceSize5&&5==chunk.currentPosition&&earlyCallback&&(earlyCallback(chunk.uuid),earlyCallback=null)}var earlyCallback=options.earlyCallback;if(delete options.earlyCallback,navigator.mozGetUserMedia&&(window.___Worker=window.Worker,delete window.Worker),window.Worker&&"function"==typeof Worker){var webWorker=processInWebWorker(fileReaderWrapper);webWorker.onmessage=function(event){processChunk(event.data)},webWorker.postMessage(options)}else fileReaderWrapper(options,processChunk),navigator.mozGetUserMedia&&(window.Worker=window.___Worker)}}function FileBufferReceiver(fbr){function receive(chunk,callback){if(!chunk.uuid)return void fbr.convertToObject(chunk,function(object){receive(object)});if(chunk.start&&!packets[chunk.uuid]&&(packets[chunk.uuid]=[],missedChunks[chunk.uuid]&&(packets[chunk.uuid].push(chunk.buffer),missedChunks[chunk.uuid].forEach(function(chunk){receive(chunk,callback)}),delete missedChunks[chunk.uuid]),fbr.onBegin&&fbr.onBegin(chunk)),!chunk.end&&chunk.buffer){if(!packets[chunk.uuid])return missedChunks[chunk.uuid]||(missedChunks[chunk.uuid]=[]),void missedChunks[chunk.uuid].push(chunk);-1==packets[chunk.uuid].indexOf(chunk.buffer)&&packets[chunk.uuid].push(chunk.buffer)}if(chunk.end){for(var _packets=packets[chunk.uuid],finalArray=[],length=_packets.length,i=0;length>i;i++)_packets[i]&&finalArray.push(_packets[i]);var blob=new Blob(finalArray,{type:chunk.type});blob=merge(blob,chunk),blob.url=URL.createObjectURL(blob),blob.uuid=chunk.uuid||blob.extra.fileUniqueId,blob.name=blob.name||blob.extra.fileName,blob.size||console.error("Something went wrong. Blob Size is 0."),fbr.onEnd&&fbr.onEnd(blob)}chunk.buffer&&fbr.onProgress&&fbr.onProgress(chunk),chunk.end||callback(chunk.uuid)}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}var packets={},missedChunks=[];this.receive=receive}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}window.FileSelector=function(){function selectFile(callback,multiple){var file=document.createElement("input");file.type="file",multiple&&(file.multiple=!0),file.onchange=function(){return multiple?file.files.length?void callback(file.files):void console.error("No file selected."):file.files[0]?(callback(file.files[0]),void file.parentNode.removeChild(file)):void console.error("No file selected.")},file.style.display="none",(document.body||document.documentElement).appendChild(file),fireClickEvent(file)}function fireClickEvent(element){var evt=new window.MouseEvent("click",{view:window,bubbles:!0,cancelable:!0,button:0,buttons:0,mozInputSource:1});element.dispatchEvent(evt)}var selector=this;selector.selectSingleFile=selectFile,selector.selectMultipleFiles=function(callback){selectFile(callback,!0)}};var FileConverter={ConvertToArrayBuffer:function(object,callback){binarize.pack(object,function(dataView){callback(dataView.buffer)})},ConvertToObject:function(buffer,callback){binarize.unpack(buffer,callback)}};!function(root){var debug=!1,BIG_ENDIAN=!1,LITTLE_ENDIAN=!0,TYPE_LENGTH=Uint8Array.BYTES_PER_ELEMENT,LENGTH_LENGTH=Uint16Array.BYTES_PER_ELEMENT,BYTES_LENGTH=Uint32Array.BYTES_PER_ELEMENT,Types={NULL:0,UNDEFINED:1,STRING:2,NUMBER:3,BOOLEAN:4,ARRAY:5,OBJECT:6,INT8ARRAY:7,INT16ARRAY:8,INT32ARRAY:9,UINT8ARRAY:10,UINT16ARRAY:11,UINT32ARRAY:12,FLOAT32ARRAY:13,FLOAT64ARRAY:14,ARRAYBUFFER:15,BLOB:16,FILE:16,BUFFER:17};if(debug)var TypeNames=["NULL","UNDEFINED","STRING","NUMBER","BOOLEAN","ARRAY","OBJECT","INT8ARRAY","INT16ARRAY","INT32ARRAY","UINT8ARRAY","UINT16ARRAY","UINT32ARRAY","FLOAT32ARRAY","FLOAT64ARRAY","ARRAYBUFFER","BLOB","BUFFER"];var Length=[null,null,"Uint16","Float64","Uint8",null,null,"Int8","Int16","Int32","Uint8","Uint16","Uint32","Float32","Float64","Uint8","Uint8","Uint8"],binary_dump=function(view,start,length){var table=[],endianness=BIG_ENDIAN,ROW_LENGTH=40;table[0]=[];for(var i=0;ROW_LENGTH>i;i++)table[0][i]=10>i?"0"+i.toString(10):i.toString(10);for(i=0;length>i;i++){var code=view.getUint8(start+i,endianness),index=~~(i/ROW_LENGTH)+1;"undefined"==typeof table[index]&&(table[index]=[]),table[index][i%ROW_LENGTH]=16>code?"0"+code.toString(16):code.toString(16)}for(console.log("%c"+table[0].join(" "),"font-weight: bold;"),i=1;ij;j++,cursor+=unit)view.setUint16(cursor,value.charCodeAt(j),endianness);break;case Types.NUMBER:case Types.BOOLEAN:debug&&console.info("%c"+value.toString(),"font-weight:bold;"),view["set"+type_name](cursor,value,endianness),cursor+=unit;break;case Types.INT8ARRAY:case Types.INT16ARRAY:case Types.INT32ARRAY:case Types.UINT8ARRAY:case Types.UINT16ARRAY:case Types.UINT32ARRAY:case Types.FLOAT32ARRAY:case Types.FLOAT64ARRAY:var _view=new Uint8Array(view.buffer,cursor,byte_length);_view.set(new Uint8Array(value.buffer)),cursor+=byte_length;break;case Types.ARRAYBUFFER:case Types.BUFFER:var _view=new Uint8Array(view.buffer,cursor,byte_length);_view.set(new Uint8Array(value)),cursor+=byte_length;break;case Types.BLOB:case Types.ARRAY:case Types.OBJECT:break;default:throw"TypeError: Unexpected type found."}debug&&binary_dump(view,start,cursor-start)}return view},unpack=function(view,cursor){var type,length,byte_length,value,elem,i=0,endianness=BIG_ENDIAN,start=cursor;type=view.getUint8(cursor,endianness),cursor+=TYPE_LENGTH,debug&&console.info("Unpacking",type,TypeNames[type]),(type===Types.ARRAY||type===Types.OBJECT)&&(length=view.getUint16(cursor,endianness),cursor+=LENGTH_LENGTH,debug&&console.info("Content Length",length)),byte_length=view.getUint32(cursor,endianness),cursor+=BYTES_LENGTH,debug&&console.info("Byte Length",byte_length,"bytes");var type_name=Length[type],unit=null===type_name?0:root[type_name+"Array"].BYTES_PER_ELEMENT;switch(type){case Types.NULL:case Types.UNDEFINED:debug&&binary_dump(view,start,cursor-start),value=null;break;case Types.STRING:length=byte_length/unit;var string=[];for(i=0;length>i;i++){var code=view.getUint16(cursor,endianness);cursor+=unit,string.push(String.fromCharCode(code))}value=string.join(""),debug&&(console.info('Actual Content %c"'+value+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.NUMBER:value=view.getFloat64(cursor,endianness),cursor+=unit,debug&&(console.info('Actual Content %c"'+value.toString()+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.BOOLEAN:value=1===view.getUint8(cursor,endianness)?!0:!1,cursor+=unit,debug&&(console.info('Actual Content %c"'+value.toString()+'"',"font-weight:bold;"),binary_dump(view,start,cursor-start));break;case Types.INT8ARRAY:case Types.INT16ARRAY:case Types.INT32ARRAY:case Types.UINT8ARRAY:case Types.UINT16ARRAY:case Types.UINT32ARRAY:case Types.FLOAT32ARRAY:case Types.FLOAT64ARRAY:case Types.ARRAYBUFFER:elem=view.buffer.slice(cursor,cursor+byte_length),cursor+=byte_length,value=type===Types.ARRAYBUFFER?elem:new root[type_name+"Array"](elem),debug&&binary_dump(view,start,cursor-start);break;case Types.BLOB:if(debug&&binary_dump(view,start,cursor-start),root.Blob){var mime=unpack(view,cursor),buffer=unpack(view,mime.cursor);cursor=buffer.cursor,value=new Blob([buffer.value],{type:mime.value})}else elem=view.buffer.slice(cursor,cursor+byte_length),cursor+=byte_length,value=new Buffer(elem);break;case Types.ARRAY:for(debug&&binary_dump(view,start,cursor-start),value=[],i=0;length>i;i++)elem=unpack(view,cursor),cursor=elem.cursor,value.push(elem.value);break;case Types.OBJECT:for(debug&&binary_dump(view,start,cursor-start),value={},i=0;length>i;i++){var key=unpack(view,cursor),val=unpack(view,key.cursor);cursor=val.cursor,value[key.value]=val.value}break;default:throw"TypeError: Type not supported."}return{value:value,cursor:cursor}},deferredSerialize=function(array,callback){for(var length=array.length,results=[],count=0,byte_length=0,i=0;i= 1.0 + 'transport': 'polling' // fixing transport:unknown issues + }; + + var socket; + + function connectSocket(connectCallback) { + if (socket) { // todo: check here readySate/etc. to make sure socket is still opened + if (connectCallback) { + connectCallback(socket); + } + return; + } + + if (typeof SocketConnection === 'undefined') { + if (typeof FirebaseConnection !== 'undefined') { + window.SocketConnection = FirebaseConnection; + } else if (typeof PubNubConnection !== 'undefined') { + window.SocketConnection = PubNubConnection; + } else { + throw 'SocketConnection.js seems missed.'; + } + } + + socket = new SocketConnection(connection, function(s) { + socket = s; + if (connectCallback) { + connectCallback(socket); + } + }); + } + + connection.openOrJoin = function(localUserid, password) { + connection.checkPresence(localUserid, function(isRoomExists, roomid) { + if (typeof password === 'function') { + password(isRoomExists, roomid); + password = null; + } + + if (isRoomExists) { + connection.sessionid = roomid; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = !!connection.session.oneway; + var isDataOnly = isData(connection.session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: password || false + }; + + mPeer.onNegotiationNeeded(connectionDescription); + return; + } + + var oldUserId = connection.userid; + connection.userid = connection.sessionid = localUserid || connection.sessionid; + connection.userid += ''; + + socket.emit('changed-uuid', connection.userid); + + if (password) { + socket.emit('set-password', password); + } + + connection.isInitiator = true; + + if (isData(connection.session)) { + return; + } + + connection.captureUserMedia(); + }); + }; + + connection.open = function(localUserid, isPublicModerator) { + var oldUserId = connection.userid; + connection.userid = connection.sessionid = localUserid || connection.sessionid; + connection.userid += ''; + + connection.isInitiator = true; + + connectSocket(function() { + socket.emit('changed-uuid', connection.userid); + + if (isPublicModerator == true) { + connection.becomePublicModerator(); + } + }); + + if (isData(connection.session)) { + if (typeof isPublicModerator === 'function') { + isPublicModerator(); + } + return; + } + + connection.captureUserMedia(typeof isPublicModerator === 'function' ? isPublicModerator : null); + }; + + connection.becomePublicModerator = function() { + if (!connection.isInitiator) return; + socket.emit('become-a-public-moderator'); + }; + + connection.dontMakeMeModerator = function() { + socket.emit('dont-make-me-moderator'); + }; + + connection.deletePeer = function(remoteUserId) { + if (!remoteUserId) { + return; + } + + connection.onleave({ + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {} + }); + + if (!!connection.peers[remoteUserId]) { + connection.peers[remoteUserId].streams.forEach(function(stream) { + stream.stop(); + }); + + var peer = connection.peers[remoteUserId].peer; + if (peer && peer.iceConnectionState !== 'closed') { + try { + peer.close(); + } catch (e) {} + } + + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].peer = null; + delete connection.peers[remoteUserId]; + } + } + + if (connection.broadcasters.indexOf(remoteUserId) !== -1) { + var newArray = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== remoteUserId) { + newArray.push(broadcaster); + } + }); + connection.broadcasters = newArray; + keepNextBroadcasterOnServer(); + } + } + + connection.rejoin = function(connectionDescription) { + if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) { + return; + } + + var extra = {}; + + if (connection.peers[connectionDescription.remoteUserId]) { + extra = connection.peers[connectionDescription.remoteUserId].extra; + connection.deletePeer(connectionDescription.remoteUserId); + } + + if (connectionDescription && connectionDescription.remoteUserId) { + connection.join(connectionDescription.remoteUserId); + + connection.onReConnecting({ + userid: connectionDescription.remoteUserId, + extra: extra + }); + } + }; + + connection.join = connection.connect = function(remoteUserId, options) { + connection.sessionid = (remoteUserId ? remoteUserId.sessionid || remoteUserId.remoteUserId || remoteUserId : false) || connection.sessionid; + connection.sessionid += ''; + + var localPeerSdpConstraints = false; + var remotePeerSdpConstraints = false; + var isOneWay = false; + var isDataOnly = false; + + if ((remoteUserId && remoteUserId.session) || !remoteUserId || typeof remoteUserId === 'string') { + var session = remoteUserId ? remoteUserId.session || connection.session : connection.session; + + isOneWay = !!session.oneway; + isDataOnly = isData(session); + + remotePeerSdpConstraints = { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + + localPeerSdpConstraints = { + OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }; + } + + options = options || {}; + if (typeof options.localPeerSdpConstraints !== 'undefined') { + localPeerSdpConstraints = options.localPeerSdpConstraints; + } + + if (typeof options.remotePeerSdpConstraints !== 'undefined') { + remotePeerSdpConstraints = options.remotePeerSdpConstraints; + } + + if (typeof options.isOneWay !== 'undefined') { + isOneWay = options.isOneWay; + } + + if (typeof options.isDataOnly !== 'undefined') { + isDataOnly = options.isDataOnly; + } + + var connectionDescription = { + remoteUserId: connection.sessionid, + message: { + newParticipationRequest: true, + isOneWay: isOneWay, + isDataOnly: isDataOnly, + localPeerSdpConstraints: localPeerSdpConstraints, + remotePeerSdpConstraints: remotePeerSdpConstraints + }, + sender: connection.userid, + password: false + }; + + connectSocket(function() { + if (!!connection.peers[connection.sessionid]) { + // on socket disconnect & reconnect + return; + } + + mPeer.onNegotiationNeeded(connectionDescription); + }); + + return connectionDescription; + }; + + connection.connectWithAllParticipants = function(remoteUserId) { + mPeer.onNegotiationNeeded('connectWithAllParticipants', remoteUserId || connection.sessionid); + }; + + connection.removeFromBroadcastersList = function(remoteUserId) { + mPeer.onNegotiationNeeded('removeFromBroadcastersList', remoteUserId || connection.sessionid); + + connection.peers.getAllParticipants(remoteUserId || connection.sessionid).forEach(function(participant) { + mPeer.onNegotiationNeeded('dropPeerConnection', participant); + connection.deletePeer(participant); + }); + + connection.attachStreams.forEach(function(stream) { + stream.stop(); + }); + }; + + connection.getUserMedia = connection.captureUserMedia = function(callback, session) { + session = session || connection.session; + + if (connection.dontCaptureUserMedia || isData(session)) { + if (callback) { + callback(); + } + return; + } + + if (session.audio || session.video || session.screen) { + if (session.screen) { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + throw error; + } + + invokeGetUserMedia({ + video: screen_constraints, + isScreen: true + }, session.audio || session.video ? invokeGetUserMedia : false); + }); + } else if (session.audio || session.video) { + invokeGetUserMedia(); + } + } + + function invokeGetUserMedia(localMediaConstraints, getUserMedia_callback) { + var isScreen = false; + if (localMediaConstraints) { + isScreen = localMediaConstraints.isScreen; + delete localMediaConstraints.isScreen; + } + + getUserMediaHandler({ + onGettingLocalMedia: function(stream) { + stream.isAudio = stream.isVideo = stream.isScreen = false; + + if (isScreen) { + stream.isScreen = true; + } else if (session.audio && session.video) { + stream.isVideo = true; + } else if (session.audio) { + stream.isAudio = true; + } + + mPeer.onGettingLocalMedia(stream); + + if (getUserMedia_callback) { + return getUserMedia_callback(); + } + + if (callback) { + callback(stream); + } + }, + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, getUserMedia_callback); + return; + } + + if (callback) { + callback(); + } + }, + localMediaConstraints: localMediaConstraints || { + audio: !!session.audio ? connection.mediaConstraints.audio : false, + video: !!session.video ? connection.mediaConstraints.video : false + } + }); + } + }; + + function beforeUnload(shiftModerationControlOnLeave, dontCloseSocket) { + if (!connection.closeBeforeUnload) { + return; + } + + if (connection.isInitiator === true) { + connection.dontMakeMeModerator(); + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + userLeft: true + }, participant); + + if (connection.peers[participant] && connection.peers[participant].peer) { + connection.peers[participant].peer.close(); + } + + delete connection.peers[participant]; + }); + + if (!dontCloseSocket) { + connection.closeSocket(); + } + + connection.broadcasters = []; + connection.isInitiator = false; + } + + connection.closeBeforeUnload = true; + window.addEventListener('beforeunload', beforeUnload, false); + + connection.userid = getRandomString(); + connection.changeUserId = function(newUserId, callback) { + connection.userid = newUserId || getRandomString(); + socket.emit('changed-uuid', connection.userid, callback || function() {}); + }; + + connection.extra = {}; + connection.attachStreams = []; + connection.removeStreams = []; + + connection.session = { + audio: true, + video: true + }; + + connection.enableFileSharing = false; + + // all values in kbps + connection.bandwidth = { + screen: 512, + audio: 128, + video: 512 + }; + + connection.codecs = { + audio: 'opus', + video: 'VP9' + }; + + connection.processSdp = function(sdp) { + if (isMobileDevice || isFirefox) { + return sdp; + } + + sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen); + sdp = CodecsHandler.setVideoBitrates(sdp, { + min: connection.bandwidth.video * 8 * 1024, + max: connection.bandwidth.video * 8 * 1024 + }); + sdp = CodecsHandler.setOpusAttributes(sdp, { + maxaveragebitrate: connection.bandwidth.audio * 8 * 1024, + maxplaybackrate: connection.bandwidth.audio * 8 * 1024, + stereo: 1, + maxptime: 3 + }); + + if (connection.codecs.video === 'VP9') { + sdp = CodecsHandler.preferVP9(sdp); + } + + if (connection.codecs.video === 'H264') { + sdp = CodecsHandler.removeVPX(sdp); + } + + if (connection.codecs.audio === 'G722') { + sdp = CodecsHandler.removeNonG722(sdp); + } + + return sdp; + }; + + if (typeof CodecsHandler !== 'undefined') { + connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler; + } + + connection.mediaConstraints = { + audio: { + mandatory: {}, + optional: [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }] + }, + video: { + mandatory: {}, + optional: [{ + bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024 + }, { + googLeakyBucket: true + }, { + facingMode: 'user' + }] + } + }; + + if (isFirefox) { + connection.mediaConstraints = { + audio: true, + video: true + }; + } + + if (!forceOptions.useDefaultDevices && !isMobileDevice) { + DetectRTC.load(function() { + var lastAudioDevice, lastVideoDevice; + // it will force RTCMultiConnection to capture last-devices + // i.e. if external microphone is attached to system, we should prefer it over built-in devices. + DetectRTC.MediaDevices.forEach(function(device) { + if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) { + lastAudioDevice = device; + } + + if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) { + lastVideoDevice = device; + } + }); + + if (lastAudioDevice) { + if (isFirefox) { + if (connection.mediaConstraints.audio !== true) { + connection.mediaConstraints.audio.deviceId = lastAudioDevice.id; + } else { + connection.mediaConstraints.audio = { + deviceId: lastAudioDevice.id + } + } + return; + } + + if (connection.mediaConstraints.audio == true) { + connection.mediaConstraints.audio = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.audio.optional) { + connection.mediaConstraints.audio.optional = []; + } + + var optional = [{ + sourceId: lastAudioDevice.id + }]; + + connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional); + } + + if (lastVideoDevice) { + if (isFirefox) { + if (connection.mediaConstraints.video !== true) { + connection.mediaConstraints.video.deviceId = lastVideoDevice.id; + } else { + connection.mediaConstraints.video = { + deviceId: lastVideoDevice.id + } + } + return; + } + + if (connection.mediaConstraints.video == true) { + connection.mediaConstraints.video = { + mandatory: {}, + optional: [] + } + } + + if (!connection.mediaConstraints.video.optional) { + connection.mediaConstraints.video.optional = []; + } + + var optional = [{ + sourceId: lastVideoDevice.id + }]; + + connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional); + } + }); + } + + connection.sdpConstraints = { + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }, + optional: [{ + VoiceActivityDetection: false + }] + }; + + connection.optionalArgument = { + optional: [{ + DtlsSrtpKeyAgreement: true + }, { + googImprovedWifiBwe: true + }, { + googScreencastMinBitrate: 300 + }, { + googIPv6: true + }, { + googDscp: true + }, { + googCpuUnderuseThreshold: 55 + }, { + googCpuOveruseThreshold: 85 + }, { + googSuspendBelowMinBitrate: true + }, { + googCpuOveruseDetection: true + }], + mandatory: {} + }; + + connection.iceServers = IceServersHandler.getIceServers(connection); + + connection.candidates = { + host: true, + stun: true, + turn: true + }; + + connection.iceProtocols = { + tcp: true, + udp: true + }; + + // EVENTs + connection.onopen = function(event) { + if (!!connection.enableLogs) { + console.info('Data connection has been opened between you & ', event.userid); + } + }; + + connection.onclose = function(event) { + if (!!connection.enableLogs) { + console.warn('Data connection has been closed between you & ', event.userid); + } + }; + + connection.onerror = function(error) { + if (!!connection.enableLogs) { + console.error(error.userid, 'data-error', error); + } + }; + + connection.onmessage = function(event) { + if (!!connection.enableLogs) { + console.debug('data-message', event.userid, event.data); + } + }; + + connection.send = function(data, remoteUserId) { + connection.peers.send(data, remoteUserId); + }; + + connection.close = connection.disconnect = connection.leave = function() { + beforeUnload(false, true); + }; + + connection.closeEntireSession = function(callback) { + callback = callback || function() {}; + socket.emit('close-entire-session', function looper() { + if (connection.getAllParticipants().length) { + setTimeout(looper, 100); + return; + } + + connection.onEntireSessionClosed({ + sessionid: connection.sessionid, + userid: connection.userid, + extra: connection.extra + }); + + connection.changeUserId(null, function() { + connection.close(); + callback(); + }); + }); + }; + + connection.onEntireSessionClosed = function(event) { + if (!connection.enableLogs) return; + console.info('Entire session is closed: ', event.sessionid, event.extra); + }; + + connection.onstream = function(e) { + var parentNode = connection.videosContainer; + parentNode.insertBefore(e.mediaElement, parentNode.firstChild); + e.mediaElement.play(); + setTimeout(function() { + e.mediaElement.play(); + }, 5000); + }; + + connection.onstreamended = function(e) { + if (!e.mediaElement) { + e.mediaElement = document.getElementById(e.streamid); + } + + if (!e.mediaElement || !e.mediaElement.parentNode) { + return; + } + + e.mediaElement.parentNode.removeChild(e.mediaElement); + }; + + connection.direction = 'many-to-many'; + + connection.removeStream = function(streamid) { + var stream; + connection.attachStreams.forEach(function(localStream) { + if (localStream.id === streamid) { + stream = localStream; + } + }); + + if (!stream) { + console.warn('No such stream exists.', streamid); + return; + } + + if (connection.removeStreams.indexOf(stream) === -1) { + connection.removeStreams.push(stream); + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.renegotiatePeer(participant); + }); + } + }; + + connection.addStream = function(session, remoteUserId) { + if (!!session.getAudioTracks) { + if (connection.attachStreams.indexOf(session) === -1) { + if (!session.streamid) { + session.streamid = session.id; + } + + connection.attachStreams.push(session); + } + connection.renegotiate(remoteUserId); + return; + } + + if (isData(session)) { + connection.renegotiate(remoteUserId); + return; + } + + if (!session.audio || session.video || session.screen) { + if (session.screen) { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + return alert(error); + } + + invokeGetUserMedia({ + video: screen_constraints + }, session.audio || session.video ? invokeGetUserMedia : false); + }); + } else if (session.audio || session.video) { + invokeGetUserMedia(); + } + } + + function invokeGetUserMedia(localMediaConstraints, callback) { + getUserMediaHandler({ + onGettingLocalMedia: function(stream) { + var videoConstraints = localMediaConstraints ? localMediaConstraints.video : connection.mediaConstraints; + if (videoConstraints) { + if (videoConstraints.mediaSource || videoConstraints.mozMediaSource) { + stream.isScreen = true; + } else if (videoConstraints.mandatory && videoConstraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } + } + + if (!stream.isScreen) { + stream.isVideo = stream.getVideoTracks().length; + stream.isAudio = !stream.isVideo && stream.getAudioTracks().length; + } + + mPeer.onGettingLocalMedia(stream); + + if (session.streamCallback) { + session.streamCallback(stream); + } + + if (callback) { + return callback(); + } + + connection.renegotiate(remoteUserId); + }, + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, callback); + return; + } + + if (callback) { + return callback(); + } + + connection.renegotiate(remoteUserId); + }, + localMediaConstraints: localMediaConstraints || { + audio: session.audio ? connection.mediaConstraints.audio : false, + video: session.video ? connection.mediaConstraints.video : false + } + }); + } + }; + + function applyConstraints(stream, mediaConstraints) { + if (!stream) { + if (!!connection.enableLogs) { + console.error('No stream to applyConstraints.'); + } + return; + } + + if (mediaConstraints.audio) { + stream.getAudioTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.audio); + }); + } + + if (mediaConstraints.video) { + stream.getVideoTracks().forEach(function(track) { + track.applyConstraints(mediaConstraints.video); + }); + } + } + + connection.applyConstraints = function(mediaConstraints, streamid) { + if (!MediaStreamTrack || !MediaStreamTrack.prototype.applyConstraints) { + alert('track.applyConstraints is NOT supported in your browser.'); + return; + } + + if (streamid) { + var streams; + if (connection.streamEvents[streamid]) { + stream = connection.streamEvents[streamid].stream; + } + applyConstraints(stream, mediaConstraints); + return; + } + + connection.attachStreams.forEach(function(stream) { + applyConstraints(stream, mediaConstraints); + }); + }; + + function replaceTrack(track, remoteUserId, isVideoTrack) { + if (remoteUserId) { + mPeer.replaceTrack(track, remoteUserId, isVideoTrack); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.replaceTrack(track, participant, isVideoTrack); + }); + } + + connection.replaceTrack = function(session, remoteUserId, isVideoTrack) { + session = session || {}; + + if (!RTCPeerConnection.prototype.getSenders) { + connection.addStream(session); + return; + } + + if (session instanceof MediaStreamTrack) { + replaceTrack(session, remoteUserId, isVideoTrack); + return; + } + + if (session instanceof MediaStream) { + if (session.getVideoTracks().length) { + replaceTrack(session.getVideoTracks()[0], remoteUserId, true); + } + + if (session.getAudioTracks().length) { + replaceTrack(session.getAudioTracks()[0], remoteUserId, false); + } + return; + } + + if (isData(session)) { + throw 'connection.replaceTrack requires audio and/or video and/or screen.'; + return; + } + + if (!session.audio || session.video || session.screen) { + if (session.screen) { + connection.getScreenConstraints(function(error, screen_constraints) { + if (error) { + return alert(error); + } + + invokeGetUserMedia({ + video: screen_constraints + }, session.audio || session.video ? invokeGetUserMedia : false); + }); + } else if (session.audio || session.video) { + invokeGetUserMedia(); + } + } + + function invokeGetUserMedia(localMediaConstraints, callback) { + getUserMediaHandler({ + onGettingLocalMedia: function(stream) { + mPeer.onGettingLocalMedia(stream); + + if (callback) { + return callback(); + } + + connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen); + }, + onLocalMediaError: function(error, constraints) { + mPeer.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, callback); + return; + } + + if (callback) { + callback(); + } + }, + localMediaConstraints: localMediaConstraints || { + audio: session.audio ? connection.mediaConstraints.audio : false, + video: session.video ? connection.mediaConstraints.video : false + } + }); + } + }; + + connection.resetTrack = function(remoteUsersIds, isVideoTrack) { + if (!remoteUsersIds) { + remoteUsersIds = connection.getAllParticipants(); + } + + if (typeof remoteUsersIds == 'string') { + remoteUsersIds = [remoteUsersIds]; + } + + remoteUsersIds.forEach(function(participant) { + var peer = connection.peers[participant].peer; + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) { + connection.replaceTrack(peer.lastVideoTrack, participant, true); + } + + if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) { + connection.replaceTrack(peer.lastAudioTrack, participant, false); + } + }); + }; + + connection.renegotiate = function(remoteUserId) { + if (remoteUserId) { + mPeer.renegotiatePeer(remoteUserId); + return; + } + + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.renegotiatePeer(participant); + }); + }; + + connection.setStreamEndHandler = function(stream, isRemote) { + if (!stream || !stream.addEventListener) return; + + isRemote = !!isRemote; + + if (stream.alreadySetEndHandler) { + return; + } + stream.alreadySetEndHandler = true; + + stream.addEventListener('ended', function() { + if (stream.idInstance) { + currentUserMediaRequest.remove(stream.idInstance); + } + + if (!isRemote) { + delete connection.attachStreams[connection.attachStreams.indexOf(stream)]; + + if (connection.removeStreams.indexOf(stream) === -1) { + connection.removeStreams.push(stream); + } + + connection.attachStreams = removeNullEntries(connection.attachStreams); + connection.removeStreams = removeNullEntries(connection.removeStreams); + } + + // connection.renegotiate(); + + var streamEvent = connection.streamEvents[stream.streamid]; + if (!streamEvent) { + streamEvent = { + stream: stream, + streamid: stream.streamid, + type: isRemote ? 'remote' : 'local', + userid: connection.userid, + extra: connection.extra, + mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null + }; + } + + if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') { + return; + } + + connection.onstreamended(streamEvent); + + delete connection.streamEvents[stream.streamid]; + }, false); + }; + + connection.onMediaError = function(error, constraints) { + if (!!connection.enableLogs) { + console.error(error, constraints); + } + }; + + connection.addNewBroadcaster = function(broadcasterId, userPreferences) { + if (connection.broadcasters.length) { + setTimeout(function() { + mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId, userPreferences, connection.broadcasters.join('|-,-|')); + }, 10 * 1000); + } + + if (!connection.session.oneway && !connection.session.broadcast && connection.direction === 'many-to-many' && connection.broadcasters.indexOf(broadcasterId) === -1) { + connection.broadcasters.push(broadcasterId); + keepNextBroadcasterOnServer(); + } + }; + + connection.autoCloseEntireSession = false; + + function keepNextBroadcasterOnServer() { + if (!connection.isInitiator) return; + + if (connection.session.oneway || connection.session.broadcast || connection.direction !== 'many-to-many') { + return; + } + + var firstBroadcaster = connection.broadcasters[0]; + var otherBroadcasters = []; + connection.broadcasters.forEach(function(broadcaster) { + if (broadcaster !== firstBroadcaster) { + otherBroadcasters.push(broadcaster); + } + }); + + if (connection.autoCloseEntireSession) return; + connection.shiftModerationControl(firstBroadcaster, otherBroadcasters, true); + }; + + connection.filesContainer = connection.videosContainer = document.body || document.documentElement; + connection.isInitiator = false; + + connection.shareFile = mPeer.shareFile; + if (typeof FileProgressBarHandler !== 'undefined') { + FileProgressBarHandler.handle(connection); + } + + if (typeof TranslationHandler !== 'undefined') { + TranslationHandler.handle(connection); + } + + connection.token = getRandomString; + + connection.onNewParticipant = function(participantId, userPreferences) { + connection.acceptParticipationRequest(participantId, userPreferences); + }; + + connection.acceptParticipationRequest = function(participantId, userPreferences) { + if (userPreferences.successCallback) { + userPreferences.successCallback(); + delete userPreferences.successCallback; + } + + mPeer.createNewPeer(participantId, userPreferences); + }; + + connection.onShiftedModerationControl = function(sender, existingBroadcasters) { + connection.acceptModerationControl(sender, existingBroadcasters); + }; + + connection.acceptModerationControl = function(sender, existingBroadcasters) { + connection.isInitiator = true; // NEW initiator! + + connection.broadcasters = existingBroadcasters; + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + changedUUID: sender, + oldUUID: connection.userid, + newUUID: sender + }, participant); + }); + connection.userid = sender; + socket.emit('changed-uuid', connection.userid); + }; + + connection.shiftModerationControl = function(remoteUserId, existingBroadcasters, firedOnLeave) { + mPeer.onNegotiationNeeded({ + shiftedModerationControl: true, + broadcasters: existingBroadcasters, + firedOnLeave: !!firedOnLeave + }, remoteUserId); + }; + + if (typeof StreamsHandler !== 'undefined') { + connection.StreamsHandler = StreamsHandler; + } + + connection.onleave = function(userid) {}; + + connection.invokeSelectFileDialog = function(callback) { + var selector = new FileSelector(); + selector.selectSingleFile(callback); + }; + + connection.getPublicModerators = function(userIdStartsWith, callback) { + if (typeof userIdStartsWith === 'function') { + callback = userIdStartsWith; + } + + connectSocket(function(socket) { + socket.emit( + 'get-public-moderators', + typeof userIdStartsWith === 'string' ? userIdStartsWith : '', + callback + ); + }); + }; + + connection.onmute = function(e) { + if (!e.mediaElement) { + return; + } + + if (e.muteType === 'both' || e.muteType === 'video') { + e.mediaElement.src = null; + e.mediaElement.pause(); + e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png'; + } else if (e.muteType === 'audio') { + e.mediaElement.muted = true; + } + }; + + connection.onunmute = function(e) { + if (!e.mediaElement) { + return; + } + + if (e.unmuteType === 'both' || e.unmuteType === 'video') { + e.mediaElement.poster = null; + e.mediaElement.src = URL.createObjectURL(e.stream); + e.mediaElement.play(); + } else if (e.unmuteType === 'audio') { + e.mediaElement.muted = false; + } + }; + + connection.onExtraDataUpdated = function(event) { + event.status = 'online'; + connection.onUserStatusChanged(event, true); + }; + + connection.onJoinWithPassword = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Please join with password.'); + }; + + connection.onInvalidPassword = function(remoteUserId, oldPassword) { + console.warn(remoteUserId, 'is password protected. Please join with valid password. Your old password', oldPassword, 'is wrong.'); + }; + + connection.onPasswordMaxTriesOver = function(remoteUserId) { + console.warn(remoteUserId, 'is password protected. Your max password tries exceeded the limit.'); + }; + + connection.getAllParticipants = function(sender) { + return connection.peers.getAllParticipants(sender); + }; + + if (typeof StreamsHandler !== 'undefined') { + StreamsHandler.onSyncNeeded = function(streamid, action, type) { + connection.peers.getAllParticipants().forEach(function(participant) { + mPeer.onNegotiationNeeded({ + streamid: streamid, + action: action, + streamSyncNeeded: true, + type: type || 'both' + }, participant); + }); + }; + } + + connection.connectSocket = function(callback) { + connectSocket(callback); + }; + + connection.closeSocket = function() { + if (!socket) return; + if (typeof socket.disconnect !== 'undefined') { + socket.disconnect(); + } + socket = null; + }; + + connection.getSocket = function(callback) { + if (!socket) { + connectSocket(callback); + } else if (callback) { + callback(socket); + } + + return socket; + }; + + connection.getRemoteStreams = mPeer.getRemoteStreams; + + var skipStreams = ['selectFirst', 'selectAll', 'forEach']; + + connection.streamEvents = { + selectFirst: function(options) { + if (!options) { + // in normal conferencing, it will always be "local-stream" + var firstStream; + for (var str in connection.streamEvents) { + if (skipStreams.indexOf(str) === -1 && !firstStream) { + firstStream = connection.streamEvents[str]; + continue; + } + } + return firstStream; + } + }, + selectAll: function() {} + }; + + connection.socketURL = '/'; // generated via config.json + connection.socketMessageEvent = 'RTCMultiConnection-Message'; // generated via config.json + connection.socketCustomEvent = 'RTCMultiConnection-Custom-Message'; // generated via config.json + connection.DetectRTC = DetectRTC; + + connection.onUserStatusChanged = function(event, dontWriteLogs) { + if (!!connection.enableLogs && !dontWriteLogs) { + console.info(event.userid, event.status); + } + }; + + connection.getUserMediaHandler = getUserMediaHandler; + connection.multiPeersHandler = mPeer; + connection.enableLogs = true; + connection.setCustomSocketHandler = function(customSocketHandler) { + if (typeof SocketConnection !== 'undefined') { + SocketConnection = customSocketHandler; + } + }; + + // default value is 15k because Firefox's receiving limit is 16k! + // however 64k works chrome-to-chrome + connection.chunkSize = 65 * 1000; + + connection.maxParticipantsAllowed = 1000; + + // eject or leave single user + connection.disconnectWith = mPeer.disconnectWith; + + connection.checkPresence = function(remoteUserId, callback) { + mPeer.onNegotiationNeeded({ + detectPresence: true, + userid: (remoteUserId || connection.sessionid) + '' + }, 'system', callback); + }; + + connection.onReadyForOffer = function(remoteUserId, userPreferences) { + connection.multiPeersHandler.createNewPeer(remoteUserId, userPreferences); + }; + + connection.setUserPreferences = function(userPreferences) { + if (connection.dontAttachStream) { + userPreferences.dontAttachLocalStream = true; + } + + if (connection.dontGetRemoteStream) { + userPreferences.dontGetRemoteStream = true; + } + + return userPreferences; + }; + + connection.updateExtraData = function() { + socket.emit('extra-data-updated', connection.extra); + }; + + connection.enableScalableBroadcast = false; + connection.maxRelayLimitPerUser = 3; // each broadcast should serve only 3 users + + connection.dontCaptureUserMedia = false; + connection.dontAttachStream = false; + connection.dontGetRemoteStream = false; + + connection.onReConnecting = function(event) { + if (connection.enableLogs) { + console.info('ReConnecting with', event.userid, '...'); + } + }; + + connection.beforeAddingStream = function(stream) { + return stream; + }; + + connection.beforeRemovingStream = function(stream) { + return stream; + }; + + if (typeof isChromeExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isChromeExtensionAvailable; + } + + if (typeof isFirefoxExtensionAvailable !== 'undefined') { + connection.checkIfChromeExtensionAvailable = isFirefoxExtensionAvailable; + } + + if (typeof getChromeExtensionStatus !== 'undefined') { + connection.getChromeExtensionStatus = getChromeExtensionStatus; + } + + connection.getScreenConstraints = function(callback) { + getScreenConstraints(function(error, screen_constraints) { + if (!error) { + screen_constraints = connection.modifyScreenConstraints(screen_constraints); + callback(error, screen_constraints); + } + }); + }; + + connection.modifyScreenConstraints = function(screen_constraints) { + return screen_constraints; + }; + + connection.onPeerStateChanged = function(state) { + if (connection.enableLogs) { + if (state.iceConnectionState.search(/closed|failed/gi) !== -1) { + console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState); + } + } + }; + + connection.isOnline = true; + + listenEventHandler('online', function() { + connection.isOnline = true; + }); + + listenEventHandler('offline', function() { + connection.isOnline = false; + }); + + connection.isLowBandwidth = false; + if (navigator && navigator.connection && navigator.connection.type) { + connection.isLowBandwidth = navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g) !== -1; + if (connection.isLowBandwidth) { + connection.bandwidth = { + audio: 30, + video: 30, + screen: 30 + }; + + if (connection.mediaConstraints.audio && connection.mediaConstraints.audio.optional.length) { + var newArray = []; + connection.mediaConstraints.audio.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.audio.optional = newArray; + } + + if (connection.mediaConstraints.video && connection.mediaConstraints.video.optional.length) { + var newArray = []; + connection.mediaConstraints.video.optional.forEach(function(opt) { + if (typeof opt.bandwidth === 'undefined') { + newArray.push(opt); + } + }); + connection.mediaConstraints.video.optional = newArray; + } + } + } + + connection.getExtraData = function(remoteUserId) { + if (!remoteUserId) throw 'remoteUserId is required.'; + if (!connection.peers[remoteUserId]) return {}; + return connection.peers[remoteUserId].extra; + }; + + if (!!forceOptions.autoOpenOrJoin) { + connection.openOrJoin(connection.sessionid); + } + + connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { + if (connection.enableLogs) { + console.warn('Userid already taken.', useridAlreadyTaken, 'Your new userid:', yourNewUserId); + } + + connection.join(useridAlreadyTaken); + }; + + connection.trickleIce = true; + } + + function SocketConnection(connection, connectCallback) { + var parameters = ''; + + parameters += '?userid=' + connection.userid; + parameters += '&msgEvent=' + connection.socketMessageEvent; + parameters += '&socketCustomEvent=' + connection.socketCustomEvent; + + if (connection.enableScalableBroadcast) { + parameters += '&enableScalableBroadcast=true'; + parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2); + } + + var socket; + + try { + socket = io((connection.socketURL || '/') + parameters); + } catch (e) { + socket = io.connect((connection.socketURL || '/') + parameters, connection.socketOptions); + } + + var mPeer = connection.multiPeersHandler; + + socket.on('extra-data-updated', function(remoteUserId, extra) { + if (!connection.peers[remoteUserId]) return; + connection.peers[remoteUserId].extra = extra; + + connection.onExtraDataUpdated({ + userid: remoteUserId, + extra: extra + }); + }); + + socket.on(connection.socketMessageEvent, function(message) { + if (message.remoteUserId != connection.userid) return; + + if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.extra) { + connection.peers[message.sender].extra = message.extra; + connection.onExtraDataUpdated({ + userid: message.sender, + extra: message.extra + }); + } + + if (message.message.streamSyncNeeded && connection.peers[message.sender]) { + var stream = connection.streamEvents[message.message.streamid]; + if (!stream || !stream.stream) { + return; + } + + var action = message.message.action; + + if (action === 'ended' || action === 'stream-removed') { + connection.onstreamended(stream); + return; + } + + var type = message.message.type != 'both' ? message.message.type : null; + stream.stream[action](type); + return; + } + + if (message.message === 'connectWithAllParticipants') { + if (connection.broadcasters.indexOf(message.sender) === -1) { + connection.broadcasters.push(message.sender); + } + + mPeer.onNegotiationNeeded({ + allParticipants: connection.getAllParticipants(message.sender) + }, message.sender); + return; + } + + if (message.message === 'removeFromBroadcastersList') { + if (connection.broadcasters.indexOf(message.sender) !== -1) { + delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)]; + connection.broadcasters = removeNullEntries(connection.broadcasters); + } + return; + } + + if (message.message === 'dropPeerConnection') { + connection.deletePeer(message.sender); + return; + } + + if (message.message.allParticipants) { + if (message.message.allParticipants.indexOf(message.sender) === -1) { + message.message.allParticipants.push(message.sender); + } + + message.message.allParticipants.forEach(function(participant) { + mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, { + localPeerSdpConstraints: { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: isData(connection.session) + }); + }); + return; + } + + if (message.message.newParticipant) { + if (message.message.newParticipant == connection.userid) return; + if (!!connection.peers[message.message.newParticipant]) return; + + mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || { + localPeerSdpConstraints: { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: isData(connection.session) + }); + return; + } + + if (message.message.readyForOffer || message.message.addMeAsBroadcaster) { + connection.addNewBroadcaster(message.sender); + } + + if (message.message.newParticipationRequest && message.sender !== connection.userid) { + if (connection.peers[message.sender]) { + connection.deletePeer(message.sender); + } + + var userPreferences = { + extra: message.extra || {}, + localPeerSdpConstraints: message.message.remotePeerSdpConstraints || { + OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + remotePeerSdpConstraints: message.message.localPeerSdpConstraints || { + OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, + OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo + }, + isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', + isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session), + dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', + dontAttachLocalStream: !!message.message.dontGetRemoteStream, + connectionDescription: message, + successCallback: function() { + // if its oneway----- todo: THIS SEEMS NOT IMPORTANT. + if (typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way') { + connection.addNewBroadcaster(message.sender, userPreferences); + } + + if (!!connection.session.oneway || connection.direction === 'one-way' || isData(connection.session)) { + connection.addNewBroadcaster(message.sender, userPreferences); + } + } + }; + + connection.onNewParticipant(message.sender, userPreferences); + return; + } + + if (message.message.shiftedModerationControl) { + connection.onShiftedModerationControl(message.sender, message.message.broadcasters); + return; + } + + if (message.message.changedUUID) { + if (connection.peers[message.message.oldUUID]) { + connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID]; + delete connection.peers[message.message.oldUUID]; + } + } + + if (message.message.userLeft) { + mPeer.onUserLeft(message.sender); + + if (!!message.message.autoCloseEntireSession) { + connection.leave(); + } + + return; + } + + mPeer.addNegotiatedMessage(message.message, message.sender); + }); + + socket.on('user-left', function(userid) { + onUserLeft(userid); + + connection.onUserStatusChanged({ + userid: userid, + status: 'offline', + extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} + }); + + connection.onleave({ + userid: userid, + extra: {} + }); + }); + + socket.on('connect', function() { + if (connection.enableLogs) { + console.info('socket.io connection is opened.'); + } + + socket.emit('extra-data-updated', connection.extra); + + if (connectCallback) connectCallback(socket); + }); + + socket.on('disconnect', function() { + if (connection.enableLogs) { + console.info('socket.io connection is closed'); + console.warn('socket.io reconnecting'); + } + }); + + socket.on('join-with-password', function(remoteUserId) { + connection.onJoinWithPassword(remoteUserId); + }); + + socket.on('invalid-password', function(remoteUserId, oldPassword) { + connection.onInvalidPassword(remoteUserId, oldPassword); + }); + + socket.on('password-max-tries-over', function(remoteUserId) { + connection.onPasswordMaxTriesOver(remoteUserId); + }); + + socket.on('user-disconnected', function(remoteUserId) { + if (remoteUserId === connection.userid) { + return; + } + + connection.onUserStatusChanged({ + userid: remoteUserId, + status: 'offline', + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {} + }); + + connection.deletePeer(remoteUserId); + }); + + socket.on('user-connected', function(userid) { + if (userid === connection.userid) { + return; + } + + connection.onUserStatusChanged({ + userid: userid, + status: 'online', + extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {} + }); + }); + + socket.on('closed-entire-session', function(sessionid, extra) { + connection.leave(); + connection.onEntireSessionClosed({ + sessionid: sessionid, + userid: sessionid, + extra: extra + }); + }); + + socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) { + connection.isInitiator = false; + connection.userid = yourNewUserId; + + connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId); + }) + + socket.on('logs', function(log) { + if (!connection.enableLogs) return; + console.debug('server-logs', log); + }); + + return socket; + } + + // MultiPeersHandler.js + + function MultiPeers(connection) { + var self = this; + + var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach']; + connection.peers = { + getLength: function() { + var numberOfPeers = 0; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1) { + numberOfPeers++; + } + } + return numberOfPeers; + }, + selectFirst: function() { + var firstPeer; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1) { + firstPeer = this[peer]; + } + } + return firstPeer; + }, + getAllParticipants: function(sender) { + var allPeers = []; + for (var peer in this) { + if (skipPeers.indexOf(peer) == -1 && peer != sender) { + allPeers.push(peer); + } + } + return allPeers; + }, + forEach: function(callbcak) { + this.getAllParticipants().forEach(function(participant) { + callbcak(connection.peers[participant]); + }); + }, + send: function(data, remoteUserId) { + var that = this; + + if (!isNull(data.size) && !isNull(data.type)) { + self.shareFile(data, remoteUserId); + return; + } + + if (data.type !== 'text' && !(data instanceof ArrayBuffer) && !(data instanceof DataView)) { + TextSender.send({ + text: data, + channel: this, + connection: connection, + remoteUserId: remoteUserId + }); + return; + } + + if (data.type === 'text') { + data = JSON.stringify(data); + } + + if (remoteUserId) { + var remoteUser = connection.peers[remoteUserId]; + if (remoteUser) { + remoteUser.channels.forEach(function(channel) { + channel.send(data); + }); + return; + } + } + + this.getAllParticipants().forEach(function(participant) { + that[participant].channels.forEach(function(channel) { + channel.send(data); + }); + }); + } + }; + + this.uuid = connection.userid; + + this.getLocalConfig = function(remoteSdp, remoteUserId, userPreferences) { + if (!userPreferences) { + userPreferences = {}; + } + + return { + streamsToShare: userPreferences.streamsToShare || {}, + rtcMultiConnection: connection, + connectionDescription: userPreferences.connectionDescription, + userid: remoteUserId, + localPeerSdpConstraints: userPreferences.localPeerSdpConstraints, + remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints, + dontGetRemoteStream: !!userPreferences.dontGetRemoteStream, + dontAttachLocalStream: !!userPreferences.dontAttachLocalStream, + renegotiatingPeer: !!userPreferences.renegotiatingPeer, + peerRef: userPreferences.peerRef, + onLocalSdp: function(localSdp) { + self.onNegotiationNeeded(localSdp, remoteUserId); + }, + onLocalCandidate: function(localCandidate) { + localCandidate = OnIceCandidateHandler.processCandidates(connection, localCandidate) + if (localCandidate) { + self.onNegotiationNeeded(localCandidate, remoteUserId); + } + }, + remoteSdp: remoteSdp, + onDataChannelMessage: function(message) { + if (!fbr && connection.enableFileSharing) initFileBufferReader(); + + if (typeof message == 'string' || !connection.enableFileSharing) { + self.onDataChannelMessage(message, remoteUserId); + return; + } + + var that = this; + + if (message instanceof ArrayBuffer || message instanceof DataView) { + fbr.convertToObject(message, function(object) { + that.onDataChannelMessage(object); + }); + return; + } + + if (message.readyForNextChunk) { + fbr.getNextChunk(message.uuid, function(nextChunk, isLastChunk) { + connection.peers[remoteUserId].channels.forEach(function(channel) { + channel.send(nextChunk); + }); + }, remoteUserId); + return; + } + + fbr.addChunk(message, function(promptNextChunk) { + connection.peers[remoteUserId].peer.channel.send(promptNextChunk); + }); + }, + onDataChannelError: function(error) { + self.onDataChannelError(error, remoteUserId); + }, + onDataChannelOpened: function(channel) { + self.onDataChannelOpened(channel, remoteUserId); + }, + onDataChannelClosed: function(event) { + self.onDataChannelClosed(event, remoteUserId); + }, + onRemoteStream: function(stream) { + connection.peers[remoteUserId].streams.push(stream); + + if (isPluginRTC && window.PluginRTC) { + var mediaElement = document.createElement('video'); + var body = connection.videosContainer; + body.insertBefore(mediaElement, body.firstChild); + setTimeout(function() { + window.PluginRTC.attachMediaStream(mediaElement, stream); + }, 3000); + return; + } + + self.onGettingRemoteMedia(stream, remoteUserId); + }, + onRemoteStreamRemoved: function(stream) { + self.onRemovingRemoteMedia(stream, remoteUserId); + }, + onPeerStateChanged: function(states) { + self.onPeerStateChanged(states); + + if (states.iceConnectionState === 'new') { + self.onNegotiationStarted(remoteUserId, states); + } + + if (states.iceConnectionState === 'connected') { + self.onNegotiationCompleted(remoteUserId, states); + } + + if (states.iceConnectionState.search(/closed|failed/gi) !== -1) { + self.onUserLeft(remoteUserId); + self.disconnectWith(remoteUserId); + } + } + }; + }; + + this.createNewPeer = function(remoteUserId, userPreferences) { + if (connection.maxParticipantsAllowed <= connection.getAllParticipants().length) { + return; + } + + userPreferences = userPreferences || {}; + + if (!userPreferences.isOneWay && !userPreferences.isDataOnly) { + userPreferences.isOneWay = true; + this.onNegotiationNeeded({ + enableMedia: true, + userPreferences: userPreferences + }, remoteUserId); + return; + } + + userPreferences = connection.setUserPreferences(userPreferences, remoteUserId); + + var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences); + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + }; + + this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) { + userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId); + + var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + }; + + this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) { + if (!connection.peers[remoteUserId]) { + if (connection.enableLogs) { + console.error('This peer (' + remoteUserId + ') does not exists. Renegotiation skipped.'); + } + return; + } + + if (!userPreferences) { + userPreferences = {}; + } + + userPreferences.renegotiatingPeer = true; + userPreferences.peerRef = connection.peers[remoteUserId].peer; + + var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences); + + connection.peers[remoteUserId] = new PeerInitiator(localConfig); + }; + + this.replaceTrack = function(track, remoteUserId, isVideoTrack) { + if (!connection.peers[remoteUserId]) { + throw 'This peer (' + remoteUserId + ') does not exists.'; + } + + var peer = connection.peers[remoteUserId].peer; + + if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) { + peer.getSenders().forEach(function(rtpSender) { + if (isVideoTrack && rtpSender.track instanceof VideoStreamTrack) { + connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track; + rtpSender.replaceTrack(track); + } + + if (!isVideoTrack && rtpSender.track instanceof AudioStreamTrack) { + connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track; + rtpSender.replaceTrack(track); + } + }); + return; + } + + console.warn('RTPSender.replaceTrack is NOT supported.'); + this.renegotiatePeer(remoteUserId); + }; + + this.onNegotiationNeeded = function(message, remoteUserId) {}; + this.addNegotiatedMessage = function(message, remoteUserId) { + if (message.type && message.sdp) { + if (message.type == 'answer') { + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].addRemoteSdp(message); + } + } + + if (message.type == 'offer') { + if (message.renegotiatingPeer) { + this.renegotiatePeer(remoteUserId, null, message); + } else { + this.createAnsweringPeer(message, remoteUserId); + } + } + + if (connection.enableLogs) { + console.log('Remote peer\'s sdp:', message.sdp); + } + return; + } + + if (message.candidate) { + if (connection.peers[remoteUserId]) { + connection.peers[remoteUserId].addRemoteCandidate(message); + } + + if (connection.enableLogs) { + console.log('Remote peer\'s candidate pairs:', message.candidate); + } + return; + } + + if (message.enableMedia) { + if (connection.attachStreams.length || connection.dontCaptureUserMedia) { + var streamsToShare = {}; + connection.attachStreams.forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + message.userPreferences.streamsToShare = streamsToShare; + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + return; + } + + var localMediaConstraints = {}; + var userPreferences = message.userPreferences; + if (userPreferences.localPeerSdpConstraints.OfferToReceiveAudio) { + localMediaConstraints.audio = connection.mediaConstraints.audio; + } + + if (userPreferences.localPeerSdpConstraints.OfferToReceiveVideo) { + localMediaConstraints.video = connection.mediaConstraints.video; + } + + invokeGetUserMedia(localMediaConstraints, message, remoteUserId); + } + + if (message.readyForOffer) { + connection.onReadyForOffer(remoteUserId, message.userPreferences); + } + }; + + function invokeGetUserMedia(mediaConstraints, message, remoteUserId) { + getUserMediaHandler({ + onGettingLocalMedia: function(localStream) { + self.onGettingLocalMedia(localStream); + + var streamsToShare = {}; + connection.attachStreams.forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + message.userPreferences.streamsToShare = streamsToShare; + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + }, + onLocalMediaError: function(error, constraints) { + self.onLocalMediaError(error, constraints); + + if (constraints.audio && constraints.video && (error.name || '').toString().indexOf('DevicesNotFound') !== -1) { + constraints.video = false; + invokeGetUserMedia(constraints, message, remoteUserId); + return; + } + + self.onNegotiationNeeded({ + readyForOffer: true, + userPreferences: message.userPreferences + }, remoteUserId); + }, + localMediaConstraints: mediaConstraints + }); + } + + + this.connectNewParticipantWithAllBroadcasters = function(newParticipantId, userPreferences, broadcastersList) { + broadcastersList = broadcastersList.split('|-,-|'); + if (!broadcastersList.length) { + return; + } + + var firstBroadcaster = broadcastersList[0]; + + self.onNegotiationNeeded({ + newParticipant: newParticipantId, + userPreferences: userPreferences || false + }, firstBroadcaster); + + delete broadcastersList[0]; + + var array = []; + broadcastersList.forEach(function(broadcaster) { + if (broadcaster) { + array.push(broadcaster); + } + }); + + setTimeout(function() { + self.connectNewParticipantWithAllBroadcasters(newParticipantId, userPreferences, array.join('|-,-|')); + }, 10 * 1000); + }; + + this.onGettingRemoteMedia = function(stream, remoteUserId) {}; + this.onRemovingRemoteMedia = function(stream, remoteUserId) {}; + this.onGettingLocalMedia = function(localStream) {}; + this.onLocalMediaError = function(error, constraints) { + connection.onMediaError(error, constraints); + }; + + var fbr; + + function initFileBufferReader() { + fbr = new FileBufferReader(); + fbr.onProgress = function(chunk) { + connection.onFileProgress(chunk); + }; + fbr.onBegin = function(file) { + connection.onFileStart(file); + }; + fbr.onEnd = function(file) { + connection.onFileEnd(file); + }; + } + + this.shareFile = function(file, remoteUserId) { + if (!connection.enableFileSharing) { + throw '"connection.enableFileSharing" is false.'; + } + + initFileBufferReader(); + + fbr.readAsArrayBuffer(file, function(uuid) { + var arrayOfUsers = connection.getAllParticipants(); + + if (remoteUserId) { + arrayOfUsers = [remoteUserId]; + } + + arrayOfUsers.forEach(function(participant) { + fbr.getNextChunk(uuid, function(nextChunk) { + connection.peers[participant].channels.forEach(function(channel) { + channel.send(nextChunk); + }); + }, participant); + }); + }, { + userid: connection.userid, + // extra: connection.extra, + chunkSize: isFirefox ? 15 * 1000 : connection.chunkSize || 0 + }); + }; + + if (typeof 'TextReceiver' !== 'undefined') { + var textReceiver = new TextReceiver(connection); + } + + this.onDataChannelMessage = function(message, remoteUserId) { + textReceiver.receive(JSON.parse(message), remoteUserId, connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}); + }; + + this.onDataChannelClosed = function(event, remoteUserId) { + event.userid = remoteUserId; + event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; + connection.onclose(event); + }; + + this.onDataChannelError = function(error, remoteUserId) { + error.userid = remoteUserId; + event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}; + connection.onerror(error); + }; + + this.onDataChannelOpened = function(channel, remoteUserId) { + // keep last channel only; we are not expecting parallel/channels channels + if (connection.peers[remoteUserId].channels.length) { + return; + } + + connection.peers[remoteUserId].channels.push(channel); + connection.onopen({ + userid: remoteUserId, + extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}, + channel: channel + }); + }; + + this.onPeerStateChanged = function(state) { + connection.onPeerStateChanged(state); + }; + + this.onNegotiationStarted = function(remoteUserId, states) {}; + this.onNegotiationCompleted = function(remoteUserId, states) {}; + + this.getRemoteStreams = function(remoteUserId) { + remoteUserId = remoteUserId || connection.peers.getAllParticipants()[0]; + return connection.peers[remoteUserId] ? connection.peers[remoteUserId].streams : []; + }; + + this.isPluginRTC = connection.isPluginRTC = isPluginRTC; + } + + // globals.js + + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + var isChrome = !!window.chrome && !isOpera; + var isIE = !!document.documentMode; + + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + isChrome = true; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + isChrome = true; + } + + var isPluginRTC = !isMobileDevice && (isSafari || isIE); + + if (isPluginRTC && typeof URL !== 'undefined') { + URL.createObjectURL = function() {}; + } + + // detect node-webkit + var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); + + var chromeVersion = 50; + var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); + if (isChrome && matchArray && matchArray[2]) { + chromeVersion = parseInt(matchArray[2], 10); + } + + var firefoxVersion = 50; + matchArray = navigator.userAgent.match(/Firefox\/(.*)/); + if (isFirefox && matchArray && matchArray[1]) { + firefoxVersion = parseInt(matchArray[1], 10); + } + + function fireEvent(obj, eventName, args) { + if (typeof CustomEvent === 'undefined') { + return; + } + + var eventDetail = { + arguments: args, + __exposedProps__: args + }; + + var event = new CustomEvent(eventName, eventDetail); + obj.dispatchEvent(event); + } + + function setHarkEvents(connection, streamEvent) { + if (!connection || !streamEvent) { + throw 'Both arguments are required.'; + } + + if (!connection.onspeaking || !connection.onsilence) { + return; + } + + if (typeof hark === 'undefined') { + throw 'hark.js not found.'; + } + + hark(streamEvent.stream, { + onspeaking: function() { + connection.onspeaking(streamEvent); + }, + onsilence: function() { + connection.onsilence(streamEvent); + }, + onvolumechange: function(volume, threshold) { + if (!connection.onvolumechange) { + return; + } + connection.onvolumechange(merge({ + volume: volume, + threshold: threshold + }, streamEvent)); + } + }); + } + + function setMuteHandlers(connection, streamEvent) { + if (!streamEvent.stream || !streamEvent.stream.addEventListener) return; + + streamEvent.stream.addEventListener('mute', function(event) { + event = connection.streamEvents[event.target.streamid]; + + event.session = { + audio: event.muteType === 'audio', + video: event.muteType === 'video' + }; + + connection.onmute(event); + }, false); + + streamEvent.stream.addEventListener('unmute', function(event) { + event = connection.streamEvents[event.target.streamid]; + + event.session = { + audio: event.unmuteType === 'audio', + video: event.unmuteType === 'video' + }; + + connection.onunmute(event); + }, false); + } + + function getRandomString() { + if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) { + var a = window.crypto.getRandomValues(new Uint32Array(3)), + token = ''; + for (var i = 0, l = a.length; i < l; i++) { + token += a[i].toString(36); + } + return token; + } else { + return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, ''); + } + } + + // Get HTMLAudioElement/HTMLVideoElement accordingly + + function getRMCMediaElement(stream, callback, connection) { + var isAudioOnly = false; + if (!!stream.getVideoTracks && !stream.getVideoTracks().length) { + isAudioOnly = true; + } + + var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video'); + + if (isPluginRTC && window.PluginRTC) { + connection.videosContainer.insertBefore(mediaElement, connection.videosContainer.firstChild); + + setTimeout(function() { + window.PluginRTC.attachMediaStream(mediaElement, stream); + callback(mediaElement); + }, 1000); + + return; + } + + // "mozSrcObject" is always preferred over "src"!! + mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.URL.createObjectURL(stream); + mediaElement.controls = true; + + // http://goo.gl/WZ5nFl + // Firefox don't yet support onended for any stream (remote/local) + if (isFirefox) { + mediaElement.addEventListener('ended', function() { + // fireEvent(stream, 'ended', stream); + currentUserMediaRequest.remove(stream.idInstance); + + if (stream.type === 'local') { + StreamsHandler.onSyncNeeded(stream.streamid, 'ended'); + + connection.attachStreams.forEach(function(aStream, idx) { + if (stream.streamid === aStream.streamid) { + delete connection.attachStreams[idx]; + } + }); + + var newStreamsArray = []; + connection.attachStreams.forEach(function(aStream) { + if (aStream) { + newStreamsArray.push(aStream); + } + }); + connection.attachStreams = newStreamsArray; + + var streamEvent = connection.streamEvents[stream.streamid]; + + if (streamEvent) { + connection.onstreamended(streamEvent); + return; + } + if (this.parentNode) { + this.parentNode.removeChild(this); + } + } + }, false); + } + + mediaElement.play(); + callback(mediaElement); + } + + // if IE + if (!window.addEventListener) { + window.addEventListener = function(el, eventName, eventHandler) { + if (!el.attachEvent) { + return; + } + el.attachEvent('on' + eventName, eventHandler); + }; + } + + function listenEventHandler(eventName, eventHandler) { + window.removeEventListener(eventName, eventHandler); + window.addEventListener(eventName, eventHandler, false); + } + + window.attachEventListener = function(video, type, listener, useCapture) { + video.addEventListener(type, listener, useCapture); + }; + + function removeNullEntries(array) { + var newArray = []; + array.forEach(function(item) { + if (item) { + newArray.push(item); + } + }); + return newArray; + } + + + function isData(session) { + return !session.audio && !session.video && !session.screen && session.data; + } + + function isNull(obj) { + return typeof obj === 'undefined'; + } + + function isString(obj) { + return typeof obj === 'string'; + } + + var MediaStream = window.MediaStream; + + if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { + MediaStream = webkitMediaStream; + } + + /*global MediaStream:true */ + if (typeof MediaStream !== 'undefined') { + if (!('getVideoTracks' in MediaStream.prototype)) { + MediaStream.prototype.getVideoTracks = function() { + if (!this.getTracks) { + return []; + } + + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('video') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; + + MediaStream.prototype.getAudioTracks = function() { + if (!this.getTracks) { + return []; + } + + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('audio') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; + } + + if (!('stop' in MediaStream.prototype)) { + MediaStream.prototype.stop = function() { + this.getAudioTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + + this.getVideoTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + }; + } + } + + // Last time updated: 2016-02-26 11:47:17 AM UTC + + // Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js + + // Muaz Khan - www.MuazKhan.com + // MIT License - www.WebRTC-Experiment.com/licence + // Documentation - github.com/muaz-khan/DetectRTC + // ____________ + // DetectRTC.js + + // DetectRTC.hasWebcam (has webcam device!) + // DetectRTC.hasMicrophone (has microphone device!) + // DetectRTC.hasSpeakers (has speakers!) + + (function() { + + 'use strict'; + + var navigator = window.navigator; + + if (typeof navigator !== 'undefined') { + if (typeof navigator.webkitGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.webkitGetUserMedia; + } + + if (typeof navigator.mozGetUserMedia !== 'undefined') { + navigator.getUserMedia = navigator.mozGetUserMedia; + } + } else { + navigator = { + getUserMedia: function() {}, + userAgent: 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45' + }; + } + + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob); + + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + var isChrome = !!window.chrome && !isOpera; + var isIE = !!document.documentMode && !isEdge; + + // this one can also be used: + // https://www.websocket.org/js/stuff.js (DetectBrowser.js) + + function getBrowserInfo() { + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var browserName = navigator.appName; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + // In Opera, the true version is after 'Opera' or after 'Version' + if (isOpera) { + browserName = 'Opera'; + try { + fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0]; + majorVersion = fullVersion.split('.')[0]; + } catch (e) { + fullVersion = '0.0.0.0'; + majorVersion = 0; + } + } + // In MSIE, the true version is after 'MSIE' in userAgent + else if (isIE) { + verOffset = nAgt.indexOf('MSIE'); + browserName = 'IE'; + fullVersion = nAgt.substring(verOffset + 5); + } + // In Chrome, the true version is after 'Chrome' + else if (isChrome) { + verOffset = nAgt.indexOf('Chrome'); + browserName = 'Chrome'; + fullVersion = nAgt.substring(verOffset + 7); + } + // In Safari, the true version is after 'Safari' or after 'Version' + else if (isSafari) { + verOffset = nAgt.indexOf('Safari'); + browserName = 'Safari'; + fullVersion = nAgt.substring(verOffset + 7); + + if ((verOffset = nAgt.indexOf('Version')) !== -1) { + fullVersion = nAgt.substring(verOffset + 8); + } + } + // In Firefox, the true version is after 'Firefox' + else if (isFirefox) { + verOffset = nAgt.indexOf('Firefox'); + browserName = 'Firefox'; + fullVersion = nAgt.substring(verOffset + 8); + } + + // In most other browsers, 'name/version' is at the end of userAgent + else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) { + browserName = nAgt.substring(nameOffset, verOffset); + fullVersion = nAgt.substring(verOffset + 1); + + if (browserName.toLowerCase() === browserName.toUpperCase()) { + browserName = navigator.appName; + } + } + + if (isEdge) { + browserName = 'Edge'; + // fullVersion = navigator.userAgent.split('Edge/')[1]; + fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10).toString(); + } + + // trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return { + fullVersion: fullVersion, + version: majorVersion, + name: browserName, + isPrivateBrowsing: false + }; + } + + // via: https://gist.github.com/cou929/7973956 + + function retry(isDone, next) { + var currentTrial = 0, + maxRetry = 50, + interval = 10, + isTimeout = false; + var id = window.setInterval( + function() { + if (isDone()) { + window.clearInterval(id); + next(isTimeout); + } + if (currentTrial++ > maxRetry) { + window.clearInterval(id); + isTimeout = true; + next(isTimeout); + } + }, + 10 + ); + } + + function isIE10OrLater(userAgent) { + var ua = userAgent.toLowerCase(); + if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) { + return false; + } + var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua); + if (match && parseInt(match[1], 10) >= 10) { + return true; + } + return false; + } + + function detectPrivateMode(callback) { + var isPrivate; + + if (window.webkitRequestFileSystem) { + window.webkitRequestFileSystem( + window.TEMPORARY, 1, + function() { + isPrivate = false; + }, + function(e) { + console.log(e); + isPrivate = true; + } + ); + } else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) { + var db; + try { + db = window.indexedDB.open('test'); + } catch (e) { + isPrivate = true; + } + + if (typeof isPrivate === 'undefined') { + retry( + function isDone() { + return db.readyState === 'done' ? true : false; + }, + function next(isTimeout) { + if (!isTimeout) { + isPrivate = db.result ? false : true; + } + } + ); + } + } else if (isIE10OrLater(window.navigator.userAgent)) { + isPrivate = false; + try { + if (!window.indexedDB) { + isPrivate = true; + } + } catch (e) { + isPrivate = true; + } + } else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) { + try { + window.localStorage.setItem('test', 1); + } catch (e) { + isPrivate = true; + } + + if (typeof isPrivate === 'undefined') { + isPrivate = false; + window.localStorage.removeItem('test'); + } + } + + retry( + function isDone() { + return typeof isPrivate !== 'undefined' ? true : false; + }, + function next(isTimeout) { + callback(isPrivate); + } + ); + } + + 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()); + }, + getOsName: function() { + var osName = 'Unknown OS'; + if (isMobile.Android()) { + osName = 'Android'; + } + + if (isMobile.BlackBerry()) { + osName = 'BlackBerry'; + } + + if (isMobile.iOS()) { + osName = 'iOS'; + } + + if (isMobile.Opera()) { + osName = 'Opera Mini'; + } + + if (isMobile.Windows()) { + osName = 'Windows'; + } + + return osName; + } + }; + + // via: http://jsfiddle.net/ChristianL/AVyND/ + function detectDesktopOS() { + var unknown = '-'; + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + + var os = unknown; + var clientStrings = [{ + s: 'Windows 10', + r: /(Windows 10.0|Windows NT 10.0)/ + }, { + s: 'Windows 8.1', + r: /(Windows 8.1|Windows NT 6.3)/ + }, { + s: 'Windows 8', + r: /(Windows 8|Windows NT 6.2)/ + }, { + s: 'Windows 7', + r: /(Windows 7|Windows NT 6.1)/ + }, { + s: 'Windows Vista', + r: /Windows NT 6.0/ + }, { + s: 'Windows Server 2003', + r: /Windows NT 5.2/ + }, { + s: 'Windows XP', + r: /(Windows NT 5.1|Windows XP)/ + }, { + s: 'Windows 2000', + r: /(Windows NT 5.0|Windows 2000)/ + }, { + s: 'Windows ME', + r: /(Win 9x 4.90|Windows ME)/ + }, { + s: 'Windows 98', + r: /(Windows 98|Win98)/ + }, { + s: 'Windows 95', + r: /(Windows 95|Win95|Windows_95)/ + }, { + s: 'Windows NT 4.0', + r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ + }, { + s: 'Windows CE', + r: /Windows CE/ + }, { + s: 'Windows 3.11', + r: /Win16/ + }, { + s: 'Android', + r: /Android/ + }, { + s: 'Open BSD', + r: /OpenBSD/ + }, { + s: 'Sun OS', + r: /SunOS/ + }, { + s: 'Linux', + r: /(Linux|X11)/ + }, { + s: 'iOS', + r: /(iPhone|iPad|iPod)/ + }, { + s: 'Mac OS X', + r: /Mac OS X/ + }, { + s: 'Mac OS', + r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ + }, { + s: 'QNX', + r: /QNX/ + }, { + s: 'UNIX', + r: /UNIX/ + }, { + s: 'BeOS', + r: /BeOS/ + }, { + s: 'OS/2', + r: /OS\/2/ + }, { + s: 'Search Bot', + r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ + }]; + for (var id in clientStrings) { + var cs = clientStrings[id]; + if (cs.r.test(nAgt)) { + os = cs.s; + break; + } + } + + var osVersion = unknown; + + if (/Windows/.test(os)) { + if (/Windows (.*)/.test(os)) { + osVersion = /Windows (.*)/.exec(os)[1]; + } + os = 'Windows'; + } + + switch (os) { + case 'Mac OS X': + if (/Mac OS X (10[\.\_\d]+)/.test(nAgt)) { + osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]; + } + break; + case 'Android': + if (/Android ([\.\_\d]+)/.test(nAgt)) { + osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]; + } + break; + case 'iOS': + if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) { + osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer); + osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0); + } + break; + } + + return { + osName: os, + osVersion: osVersion + }; + } + + var osName = 'Unknown OS'; + var osVersion = 'Unknown OS Version'; + + if (isMobile.any()) { + osName = isMobile.getOsName(); + } else { + var osInfo = detectDesktopOS(); + osName = osInfo.osName; + osVersion = osInfo.osVersion; + } + + var isCanvasSupportsStreamCapturing = false; + var isVideoSupportsStreamCapturing = false; + ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) { + if (!isCanvasSupportsStreamCapturing && item in document.createElement('canvas')) { + isCanvasSupportsStreamCapturing = true; + } + + if (!isVideoSupportsStreamCapturing && item in document.createElement('video')) { + isVideoSupportsStreamCapturing = true; + } + }); + + // via: https://github.com/diafygi/webrtc-ips + function DetectLocalIPAddress(callback) { + if (!DetectRTC.isWebRTCSupported) { + return; + } + + if (DetectRTC.isORTCSupported) { + return; + } + + getIPs(function(ip) { + //local IPs + if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) { + callback('Local: ' + ip); + } + + //assume the rest are public IPs + else { + callback('Public: ' + ip); + } + }); + } + + //get the IP addresses associated with an account + function getIPs(callback) { + var ipDuplicates = {}; + + //compatibility for firefox and chrome + var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; + var useWebKit = !!window.webkitRTCPeerConnection; + + // bypass naive webrtc blocking using an iframe + if (!RTCPeerConnection) { + var iframe = document.getElementById('iframe'); + if (!iframe) { + // + throw 'NOTE: you need to have an iframe in the page right above the script tag.'; + } + var win = iframe.contentWindow; + RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection; + useWebKit = !!win.webkitRTCPeerConnection; + } + + // if still no RTCPeerConnection then it is not supported by the browser so just return + if (!RTCPeerConnection) { + return; + } + + //minimal requirements for data connection + var mediaConstraints = { + optional: [{ + RtpDataChannels: true + }] + }; + + //firefox already has a default stun server in about:config + // media.peerconnection.default_iceservers = + // [{"url": "stun:stun.services.mozilla.com"}] + var servers; + + //add same stun server for chrome + if (useWebKit) { + servers = { + iceServers: [{ + urls: 'stun:stun.services.mozilla.com' + }] + }; + + if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isFirefox && DetectRTC.browser.version <= 38) { + servers[0] = { + url: servers[0].urls + }; + } + } + + //construct a new RTCPeerConnection + var pc = new RTCPeerConnection(servers, mediaConstraints); + + function handleCandidate(candidate) { + //match just the IP address + var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/; + var match = ipRegex.exec(candidate); + if (!match) { + console.warn('Could not match IP address in', candidate); + return; + } + var ipAddress = match[1]; + + //remove duplicates + if (ipDuplicates[ipAddress] === undefined) { + callback(ipAddress); + } + + ipDuplicates[ipAddress] = true; + } + + //listen for candidate events + pc.onicecandidate = function(ice) { + //skip non-candidate events + if (ice.candidate) { + handleCandidate(ice.candidate.candidate); + } + }; + + //create a bogus data channel + pc.createDataChannel(''); + + //create an offer sdp + pc.createOffer(function(result) { + + //trigger the stun server request + pc.setLocalDescription(result, function() {}, function() {}); + + }, function() {}); + + //wait for a while to let everything done + setTimeout(function() { + //read candidate info from local description + var lines = pc.localDescription.sdp.split('\n'); + + lines.forEach(function(line) { + if (line.indexOf('a=candidate:') === 0) { + handleCandidate(line); + } + }); + }, 1000); + } + + var MediaDevices = []; + + var audioInputDevices = []; + var audioOutputDevices = []; + var videoInputDevices = []; + + if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { + // Firefox 38+ seems having support of enumerateDevices + // Thanks @xdumaine/enumerateDevices + navigator.enumerateDevices = function(callback) { + navigator.mediaDevices.enumerateDevices().then(callback); + }; + } + + // ---------- Media Devices detection + var canEnumerate = false; + + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) { + canEnumerate = true; + } else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) { + canEnumerate = true; + } + + var hasMicrophone = false; + var hasSpeakers = false; + var hasWebcam = false; + + var isWebsiteHasMicrophonePermissions = false; + var isWebsiteHasWebcamPermissions = false; + + // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices + // todo: switch to enumerateDevices when landed in canary. + function checkDeviceSupport(callback) { + if (!canEnumerate) { + return; + } + + // This method is useful only for Chrome! + + if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) { + navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack); + } + + if (!navigator.enumerateDevices && navigator.enumerateDevices) { + navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); + } + + if (!navigator.enumerateDevices) { + if (callback) { + callback(); + } + return; + } + + MediaDevices = []; + + audioInputDevices = []; + audioOutputDevices = []; + videoInputDevices = []; + + navigator.enumerateDevices(function(devices) { + devices.forEach(function(_device) { + var device = {}; + for (var d in _device) { + device[d] = _device[d]; + } + + // if it is MediaStreamTrack.getSources + if (device.kind === 'audio') { + device.kind = 'audioinput'; + } + + if (device.kind === 'video') { + device.kind = 'videoinput'; + } + + var skip; + MediaDevices.forEach(function(d) { + if (d.id === device.id && d.kind === device.kind) { + skip = true; + } + }); + + if (skip) { + return; + } + + if (!device.deviceId) { + device.deviceId = device.id; + } + + if (!device.id) { + device.id = device.deviceId; + } + + if (!device.label) { + device.label = 'Please invoke getUserMedia once.'; + if (location.protocol !== 'https:') { + if (document.domain.search && document.domain.search(/localhost|127.0./g) === -1) { + device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.'; + } + } + } else { + if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) { + isWebsiteHasWebcamPermissions = true; + } + + if (device.kind === 'audioinput' && !isWebsiteHasMicrophonePermissions) { + isWebsiteHasMicrophonePermissions = true; + } + } + + if (device.kind === 'audioinput') { + hasMicrophone = true; + + if (audioInputDevices.indexOf(device) === -1) { + audioInputDevices.push(device); + } + } + + if (device.kind === 'audiooutput') { + hasSpeakers = true; + + if (audioOutputDevices.indexOf(device) === -1) { + audioOutputDevices.push(device); + } + } + + if (device.kind === 'videoinput') { + hasWebcam = true; + + if (videoInputDevices.indexOf(device) === -1) { + videoInputDevices.push(device); + } + } + + // there is no 'videoouput' in the spec. + + if (MediaDevices.indexOf(device) === -1) { + MediaDevices.push(device); + } + }); + + if (typeof DetectRTC !== 'undefined') { + // to sync latest outputs + DetectRTC.MediaDevices = MediaDevices; + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; + + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; + } + + if (callback) { + callback(); + } + }); + } + + // check for microphone/camera support! + checkDeviceSupport(); + + var DetectRTC = window.DetectRTC || {}; + + // ---------- + // DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion + DetectRTC.browser = getBrowserInfo(); + + detectPrivateMode(function(isPrivateBrowsing) { + DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing; + }); + + // DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge + DetectRTC.browser['is' + DetectRTC.browser.name] = true; + + var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']); + + // --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1. + var isWebRTCSupported = false; + ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) { + if (isWebRTCSupported) { + return; + } + + if (item in window) { + isWebRTCSupported = true; + } + }); + DetectRTC.isWebRTCSupported = isWebRTCSupported; + + //------- + DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined'; + + // --------- Detect if system supports screen capturing API + var isScreenCapturingSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) { + isScreenCapturingSupported = true; + } else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) { + isScreenCapturingSupported = true; + } + + if (location.protocol !== 'https:') { + isScreenCapturingSupported = false; + } + DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported; + + // --------- Detect if WebAudio API are supported + var webAudio = { + isSupported: false, + isCreateMediaStreamSourceSupported: false + }; + + ['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) { + if (webAudio.isSupported) { + return; + } + + if (item in window) { + webAudio.isSupported = true; + + if ('createMediaStreamSource' in window[item].prototype) { + webAudio.isCreateMediaStreamSourceSupported = true; + } + } + }); + DetectRTC.isAudioContextSupported = webAudio.isSupported; + DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported; + + // ---------- Detect if SCTP/RTP channels are supported. + + var isRtpDataChannelsSupported = false; + if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) { + isRtpDataChannelsSupported = true; + } + DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported; + + var isSCTPSupportd = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) { + isSCTPSupportd = true; + } else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) { + isSCTPSupportd = true; + } + DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd; + + // --------- + + DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js" + + // ------ + var isGetUserMediaSupported = false; + if (navigator.getUserMedia) { + isGetUserMediaSupported = true; + } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + isGetUserMediaSupported = true; + } + if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && location.protocol !== 'https:') { + DetectRTC.isGetUserMediaSupported = 'Requires HTTPs'; + } + DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported; + + // ----------- + DetectRTC.osName = osName; + DetectRTC.osVersion = osVersion; + + var displayResolution = ''; + if (screen.width) { + var width = (screen.width) ? screen.width : ''; + var height = (screen.height) ? screen.height : ''; + displayResolution += '' + width + ' x ' + height; + } + DetectRTC.displayResolution = displayResolution; + + // ---------- + DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing; + DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing; + + // ------ + DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress; + + DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING; + DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported; + + DetectRTC.checkWebSocketsSupport = function(callback) { + callback = callback || function() {}; + try { + var websocket = new WebSocket('wss://echo.websocket.org:443/'); + websocket.onopen = function() { + DetectRTC.isWebSocketsBlocked = false; + callback(); + websocket.close(); + websocket = null; + }; + websocket.onerror = function() { + DetectRTC.isWebSocketsBlocked = true; + callback(); + }; + } catch (e) { + DetectRTC.isWebSocketsBlocked = true; + callback(); + } + }; + + // ------- + DetectRTC.load = function(callback) { + callback = callback || function() {}; + checkDeviceSupport(callback); + }; + + DetectRTC.MediaDevices = MediaDevices; + DetectRTC.hasMicrophone = hasMicrophone; + DetectRTC.hasSpeakers = hasSpeakers; + DetectRTC.hasWebcam = hasWebcam; + + DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions; + DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions; + + DetectRTC.audioInputDevices = audioInputDevices; + DetectRTC.audioOutputDevices = audioOutputDevices; + DetectRTC.videoInputDevices = videoInputDevices; + + // ------ + var isSetSinkIdSupported = false; + if ('setSinkId' in document.createElement('video')) { + isSetSinkIdSupported = true; + } + DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported; + + // ----- + var isRTPSenderReplaceTracksSupported = false; + if (DetectRTC.browser.isFirefox /*&& DetectRTC.browser.version > 39*/ ) { + /*global mozRTCPeerConnection:true */ + if ('getSenders' in mozRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } + } else if (DetectRTC.browser.isChrome && typeof webkitRTCPeerConnection !== 'undefined') { + /*global webkitRTCPeerConnection:true */ + if ('getSenders' in webkitRTCPeerConnection.prototype) { + isRTPSenderReplaceTracksSupported = true; + } + } + DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported; + + //------ + var isRemoteStreamProcessingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) { + isRemoteStreamProcessingSupported = true; + } + DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported; + + //------- + var isApplyConstraintsSupported = false; + + /*global MediaStreamTrack:true */ + if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) { + isApplyConstraintsSupported = true; + } + DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported; + + //------- + var isMultiMonitorScreenCapturingSupported = false; + if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) { + // version 43 merely supports platforms for multi-monitors + // version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing. + isMultiMonitorScreenCapturingSupported = true; + } + DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported; + + window.DetectRTC = DetectRTC; + + })(); + + // ios-hacks.js + + function setCordovaAPIs() { + if (DetectRTC.osName !== 'iOS') return; + if (typeof cordova === 'undefined' || typeof cordova.plugins === 'undefined' || typeof cordova.plugins.iosrtc === 'undefined') return; + + var iosrtc = cordova.plugins.iosrtc; + window.webkitRTCPeerConnection = iosrtc.RTCPeerConnection; + window.RTCSessionDescription = iosrtc.RTCSessionDescription; + window.RTCIceCandidate = iosrtc.RTCIceCandidate; + window.MediaStream = iosrtc.MediaStream; + window.MediaStreamTrack = iosrtc.MediaStreamTrack; + navigator.getUserMedia = navigator.webkitGetUserMedia = iosrtc.getUserMedia; + + iosrtc.debug.enable('iosrtc*'); + iosrtc.registerGlobals(); + } + + document.addEventListener('deviceready', setCordovaAPIs, false); + setCordovaAPIs(); + + // RTCPeerConnection.js + + var defaults = {}; + + function setSdpConstraints(config) { + var sdpConstraints; + + var sdpConstraints_mandatory = { + OfferToReceiveAudio: !!config.OfferToReceiveAudio, + OfferToReceiveVideo: !!config.OfferToReceiveVideo + }; + + sdpConstraints = { + mandatory: sdpConstraints_mandatory, + optional: [{ + VoiceActivityDetection: false + }] + }; + + if (!!navigator.mozGetUserMedia && firefoxVersion > 34) { + sdpConstraints = { + OfferToReceiveAudio: !!config.OfferToReceiveAudio, + OfferToReceiveVideo: !!config.OfferToReceiveVideo + }; + } + + return sdpConstraints; + } + + var RTCPeerConnection; + if (typeof mozRTCPeerConnection !== 'undefined') { + RTCPeerConnection = mozRTCPeerConnection; + } else if (typeof webkitRTCPeerConnection !== 'undefined') { + RTCPeerConnection = webkitRTCPeerConnection; + } else if (typeof window.RTCPeerConnection !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } + + var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription; + var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; + var MediaStreamTrack = window.MediaStreamTrack; + + window.onPluginRTCInitialized = function() { + MediaStreamTrack = window.PluginRTC.MediaStreamTrack; + RTCPeerConnection = window.PluginRTC.RTCPeerConnection; + RTCIceCandidate = window.PluginRTC.RTCIceCandidate; + RTCSessionDescription = window.PluginRTC.RTCSessionDescription; + } + + if (typeof window.PluginRTC !== 'undefined') { + window.onPluginRTCInitialized(); + } + + function PeerInitiator(config) { + if (!RTCPeerConnection) { + throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.'; + } + + var connection = config.rtcMultiConnection; + + this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra; + this.userid = config.userid; + this.streams = []; + this.channels = []; + this.connectionDescription = config.connectionDescription; + + var that = this; + + if (config.remoteSdp) { + this.connectionDescription = config.remoteSdp.connectionDescription; + } + + var allRemoteStreams = {}; + + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + }); + + var peer; + + var renegotiatingPeer = !!config.renegotiatingPeer; + if (config.remoteSdp) { + renegotiatingPeer = !!config.remoteSdp.renegotiatingPeer; + } + + var localStreams = []; + connection.attachStreams.forEach(function(stream) { + if (!!stream) localStreams.push(stream); + }); + + if (!renegotiatingPeer) { + peer = new RTCPeerConnection(navigator.onLine ? { + iceServers: connection.iceServers, + iceTransports: 'all' + } : null, window.PluginRTC ? null : connection.optionalArgument); + } else { + peer = config.peerRef; + + peer.getLocalStreams().forEach(function(stream) { + localStreams.forEach(function(localStream, index) { + if (stream == localStream) { + delete localStreams[index]; + } + }); + + connection.removeStreams.forEach(function(streamToRemove, index) { + if (stream === streamToRemove) { + stream = connection.beforeRemovingStream(stream); + if (stream && !!peer.removeStream) { + peer.removeStream(stream); + } + + localStreams.forEach(function(localStream, index) { + if (streamToRemove == localStream) { + delete localStreams[index]; + } + }); + } + }); + }); + } + + if (connection.DetectRTC.browser.name === 'Firefox') { + peer.removeStream = function(stream) { + stream.mute(); + connection.StreamsHandler.onSyncNeeded(stream.streamid, 'stream-removed'); + }; + } + + peer.onicecandidate = function(event) { + if (!event.candidate) { + if (!connection.trickleIce) { + var localSdp = peer.localDescription; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: that.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare, + isFirefoxOffered: isFirefox + }); + } + return; + } + + if (!connection.trickleIce) return; + config.onLocalCandidate({ + candidate: event.candidate.candidate, + sdpMid: event.candidate.sdpMid, + sdpMLineIndex: event.candidate.sdpMLineIndex + }); + }; + + var isFirefoxOffered = !isFirefox; + if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered) { + isFirefoxOffered = true; + } + + localStreams.forEach(function(localStream) { + if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream) { + return; + } + + if (config.dontAttachLocalStream) { + return; + } + + localStream = connection.beforeAddingStream(localStream); + if (localStream) { + peer.addStream(localStream); + } + }); + + peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() { + var extra = that.extra; + if (connection.peers[that.userid]) { + extra = connection.peers[that.userid].extra || extra; + } + + if (!peer) { + return; + } + + config.onPeerStateChanged({ + iceConnectionState: peer.iceConnectionState, + iceGatheringState: peer.iceGatheringState, + signalingState: peer.signalingState, + extra: extra, + userid: that.userid + }); + }; + + var sdpConstraints = { + OfferToReceiveAudio: !!localStreams.length, + OfferToReceiveVideo: !!localStreams.length + }; + + if (config.localPeerSdpConstraints) sdpConstraints = config.localPeerSdpConstraints; + + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + + peer.onaddstream = function(event) { + var streamsToShare = {}; + if (config.remoteSdp && config.remoteSdp.streamsToShare) { + streamsToShare = config.remoteSdp.streamsToShare; + } else if (config.streamsToShare) { + streamsToShare = config.streamsToShare; + } + + var streamToShare = streamsToShare[event.stream.id]; + if (streamToShare) { + event.stream.isAudio = streamToShare.isAudio; + event.stream.isVideo = streamToShare.isVideo; + event.stream.isScreen = streamToShare.isScreen; + } + event.stream.streamid = event.stream.id; + if (!event.stream.stop) { + event.stream.stop = function() { + if (isFirefox) { + fireEvent(this, 'ended'); + } + }; + } + allRemoteStreams[event.stream.id] = event.stream; + config.onRemoteStream(event.stream); + }; + + peer.onremovestream = function(event) { + event.stream.streamid = event.stream.id; + + if (allRemoteStreams[event.stream.id]) { + delete allRemoteStreams[event.stream.id]; + } + + config.onRemoteStreamRemoved(event.stream); + }; + + this.addRemoteCandidate = function(remoteCandidate) { + peer.addIceCandidate(new RTCIceCandidate(remoteCandidate)); + }; + + this.addRemoteSdp = function(remoteSdp) { + remoteSdp.sdp = connection.processSdp(remoteSdp.sdp); + peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), function() {}, function(error) { + if (!!connection.enableLogs) { + console.error(JSON.stringify(error, null, '\t'), '\n', remoteSdp.type, remoteSdp.sdp); + } + }); + }; + + var isOfferer = true; + + if (config.remoteSdp) { + isOfferer = false; + } + + if (connection.session.data === true) { + createDataChannel(); + } + + if (config.remoteSdp) { + if (config.remoteSdp.remotePeerSdpConstraints) { + sdpConstraints = config.remoteSdp.remotePeerSdpConstraints; + } + defaults.sdpConstraints = setSdpConstraints(sdpConstraints); + this.addRemoteSdp(config.remoteSdp); + } + + function createDataChannel() { + if (!isOfferer) { + peer.ondatachannel = function(event) { + var channel = event.channel; + setChannelEvents(channel); + }; + return; + } + + var channel = peer.createDataChannel('RTCDataChannel', {}); + setChannelEvents(channel); + } + + function setChannelEvents(channel) { + // force ArrayBuffer in Firefox; which uses "Blob" by default. + channel.binaryType = 'arraybuffer'; + + channel.onmessage = function(event) { + config.onDataChannelMessage(event.data); + }; + + channel.onopen = function() { + config.onDataChannelOpened(channel); + }; + + channel.onerror = function(error) { + config.onDataChannelError(error); + }; + + channel.onclose = function(event) { + config.onDataChannelClosed(event); + }; + + channel.internalSend = channel.send; + channel.send = function(data) { + if (channel.readyState !== 'open') { + return; + } + + channel.internalSend(data); + }; + + peer.channel = channel; + } + + if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') { + defaults.sdpConstraints = setSdpConstraints({ + OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio), + OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio) + }); + } + + var streamsToShare = {}; + peer.getLocalStreams().forEach(function(stream) { + streamsToShare[stream.streamid] = { + isAudio: !!stream.isAudio, + isVideo: !!stream.isVideo, + isScreen: !!stream.isScreen + }; + }); + + peer[isOfferer ? 'createOffer' : 'createAnswer'](function(localSdp) { + localSdp.sdp = connection.processSdp(localSdp.sdp); + peer.setLocalDescription(localSdp); + + if (!connection.trickleIce) return; + config.onLocalSdp({ + type: localSdp.type, + sdp: localSdp.sdp, + remotePeerSdpConstraints: config.remotePeerSdpConstraints || false, + renegotiatingPeer: !!config.renegotiatingPeer || false, + connectionDescription: that.connectionDescription, + dontGetRemoteStream: !!config.dontGetRemoteStream, + extra: connection ? connection.extra : {}, + streamsToShare: streamsToShare, + isFirefoxOffered: isFirefox + }); + }, function(error) { + if (!!connection.enableLogs) { + console.error('sdp-error', error); + } + }, defaults.sdpConstraints); + + peer.nativeClose = peer.close; + peer.close = function() { + if (!peer) { + return; + } + + try { + if (peer.iceConnectionState.search(/closed|failed/gi) === -1) { + peer.getRemoteStreams().forEach(function(stream) { + stream.stop(); + }); + } + peer.nativeClose(); + } catch (e) {} + + peer = null; + that.peer = null; + }; + + this.peer = peer; + } + + // CodecsHandler.js + + var CodecsHandler = (function() { + var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i); + if (typeof cordova !== 'undefined') { + isMobileDevice = true; + } + + if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) { + isMobileDevice = true; + } + + // "removeVPX" and "removeNonG722" methods are taken from github/mozilla/webrtc-landing + function removeVPX(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + // this method is NOT reliable + + sdp = sdp.replace('a=rtpmap:100 VP8/90000\r\n', ''); + sdp = sdp.replace('a=rtpmap:101 VP9/90000\r\n', ''); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g, 'm=video $1 RTP\/SAVPF $2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g, 'm=video $1 RTP\/SAVPF $2'); + + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + sdp = sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g, 'm=video $1 RTP\/SAVPF$2'); + + sdp = sdp.replace('a=rtcp-fb:120 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:120 ccm fir\r\n', ''); + + sdp = sdp.replace('a=rtcp-fb:101 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 nack pli\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:101 ccm fir\r\n', ''); + + return sdp; + } + + function disableNACK(sdp) { + if (!sdp || typeof sdp !== 'string') { + throw 'Invalid arguments.'; + } + + sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n'); + sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', ''); + sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n'); + + return sdp; + } + + function prioritize(codecMimeType, peer) { + if (!peer || !peer.getSenders || !peer.getSenders().length) { + return; + } + + if (!codecMimeType || typeof codecMimeType !== 'string') { + throw 'Invalid arguments.'; + } + + peer.getSenders().forEach(function(sender) { + var params = sender.getParameters(); + for (var i = 0; i < params.codecs.length; i++) { + if (params.codecs[i].mimeType == codecMimeType) { + params.codecs.unshift(params.codecs.splice(i, 1)); + break; + } + } + sender.setParameters(params); + }); + } + + function removeNonG722(sdp) { + return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9'); + } + + function setBAS(sdp, bandwidth, isScreen) { + if (!bandwidth) { + return sdp; + } + + if (typeof isFirefox !== 'undefined' && isFirefox) { + return sdp; + } + + if (isMobileDevice) { + return sdp; + } + + if (isScreen) { + if (!bandwidth.screen) { + console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.'); + } else if (bandwidth.screen < 300) { + console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.'); + } + } + + // if screen; must use at least 300kbs + if (bandwidth.screen && isScreen) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n'); + } + + // remove existing bandwidth lines + if (bandwidth.audio || bandwidth.video || bandwidth.data) { + sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, ''); + } + + if (bandwidth.audio) { + sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n'); + } + + if (bandwidth.video) { + sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n'); + } + + return sdp; + } + + // Find the line in sdpLines that starts with |prefix|, and, if specified, + // contains |substr| (case-insensitive search). + function findLine(sdpLines, prefix, substr) { + return findLineInRange(sdpLines, 0, -1, prefix, substr); + } + + // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix| + // and, if specified, contains |substr| (case-insensitive search). + function findLineInRange(sdpLines, startLine, endLine, prefix, substr) { + var realEndLine = endLine !== -1 ? endLine : sdpLines.length; + for (var i = startLine; i < realEndLine; ++i) { + if (sdpLines[i].indexOf(prefix) === 0) { + if (!substr || + sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) { + return i; + } + } + } + return null; + } + + // Gets the codec payload type from an a=rtpmap:X line. + function getCodecPayloadType(sdpLine) { + var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+'); + var result = sdpLine.match(pattern); + return (result && result.length === 2) ? result[1] : null; + } + + function setVideoBitrates(sdp, params) { + if (isMobileDevice) { + return sdp; + } + + params = params || {}; + var xgoogle_min_bitrate = params.min; + var xgoogle_max_bitrate = params.max; + + var sdpLines = sdp.split('\r\n'); + + // VP8 + var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000'); + var vp8Payload; + if (vp8Index) { + vp8Payload = getCodecPayloadType(sdpLines[vp8Index]); + } + + if (!vp8Payload) { + return sdp; + } + + var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000'); + var rtxPayload; + if (rtxIndex) { + rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]); + } + + if (!rtxIndex) { + return sdp; + } + + var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString()); + if (rtxFmtpLineIndex !== null) { + var appendrtxNext = '\r\n'; + appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228'); + sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext); + sdp = sdpLines.join('\r\n'); + } + + return sdp; + } + + function setOpusAttributes(sdp, params) { + if (isMobileDevice) { + return sdp; + } + + params = params || {}; + + var sdpLines = sdp.split('\r\n'); + + // Opus + var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'); + var opusPayload; + if (opusIndex) { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + if (!opusPayload) { + return sdp; + } + + var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + if (opusFmtpLineIndex === null) { + return sdp; + } + + var appendOpusNext = ''; + appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1'); + appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1'); + + if (typeof params.maxaveragebitrate != 'undefined') { + appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8); + } + + if (typeof params.maxplaybackrate != 'undefined') { + appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8); + } + + if (typeof params.cbr != 'undefined') { + appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1'); + } + + if (typeof params.useinbandfec != 'undefined') { + appendOpusNext += '; useinbandfec=' + params.useinbandfec; + } + + if (typeof params.usedtx != 'undefined') { + appendOpusNext += '; usedtx=' + params.usedtx; + } + + if (typeof params.maxptime != 'undefined') { + appendOpusNext += '\r\na=maxptime:' + params.maxptime; + } + + sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext); + + sdp = sdpLines.join('\r\n'); + return sdp; + } + + function preferVP9(sdp) { + if (sdp.indexOf('SAVPF 100 101') === -1 || sdp.indexOf('VP9/90000') === -1) { + return sdp; + } + + return sdp.replace('SAVPF 100 101', 'SAVPF 101 100'); + } + + return { + removeVPX: removeVPX, + disableNACK: disableNACK, + prioritize: prioritize, + removeNonG722: removeNonG722, + setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) { + return setBAS(sdp, bandwidth, isScreen); + }, + setVideoBitrates: function(sdp, params) { + return setVideoBitrates(sdp, params); + }, + setOpusAttributes: function(sdp, params) { + return setOpusAttributes(sdp, params); + }, + preferVP9: preferVP9 + }; + })(); + + // backward compatibility + window.BandwidthHandler = CodecsHandler; + + // OnIceCandidateHandler.js + + var OnIceCandidateHandler = (function() { + function processCandidates(connection, icePair) { + var candidate = icePair.candidate; + + var iceRestrictions = connection.candidates; + var stun = iceRestrictions.stun; + var turn = iceRestrictions.turn; + + if (!isNull(iceRestrictions.reflexive)) { + stun = iceRestrictions.reflexive; + } + + if (!isNull(iceRestrictions.relay)) { + turn = iceRestrictions.relay; + } + + if (!iceRestrictions.host && !!candidate.match(/typ host/g)) { + return; + } + + if (!turn && !!candidate.match(/typ relay/g)) { + return; + } + + if (!stun && !!candidate.match(/typ srflx/g)) { + return; + } + + var protocol = connection.iceProtocols; + + if (!protocol.udp && !!candidate.match(/ udp /g)) { + return; + } + + if (!protocol.tcp && !!candidate.match(/ tcp /g)) { + return; + } + + if (connection.enableLogs) { + console.debug('Your candidate pairs:', candidate); + } + + return { + candidate: candidate, + sdpMid: icePair.sdpMid, + sdpMLineIndex: icePair.sdpMLineIndex + }; + } + + return { + processCandidates: processCandidates + }; + })(); + + // IceServersHandler.js + + var iceFrame, loadedIceFrame; + + function loadIceFrame(callback, skip) { + if (loadedIceFrame) return; + if (!skip) return loadIceFrame(callback, true); + + loadedIceFrame = true; + + var iframe = document.createElement('iframe'); + iframe.onload = function() { + iframe.isLoaded = true; + + listenEventHandler('message', iFrameLoaderCallback); + + function iFrameLoaderCallback(event) { + if (!event.data || !event.data.iceServers) return; + callback(event.data.iceServers); + window.removeEventListener('message', iFrameLoaderCallback); + } + + iframe.contentWindow.postMessage('get-ice-servers', '*'); + }; + iframe.src = 'https://cdn.webrtc-experiment.com/getIceServers/'; + iframe.style.display = 'none'; + (document.body || document.documentElement).appendChild(iframe); + } + + if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { + loadIceFrame(function(externalIceServers) { + if (!externalIceServers || !externalIceServers.length) return; + window.RMCExternalIceServers = externalIceServers; + + if (window.iceServersLoadCallback && typeof window.iceServersLoadCallback === 'function') { + window.iceServersLoadCallback(externalIceServers); + } + }); + } + + function getSTUNObj(stunStr) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; + } + + var obj = {}; + obj[urlsParam] = stunStr; + return obj; + } + + function getTURNObj(turnStr, username, credential) { + var urlsParam = 'urls'; + if (isPluginRTC) { + urlsParam = 'url'; + } + + var obj = { + username: username, + credential: credential + }; + obj[urlsParam] = turnStr; + return obj; + } + + function getExtenralIceFormatted() { + var iceServers; + window.RMCExternalIceServers.forEach(function(ice) { + if (!ice.urls) { + ice.urls = ice.url; + } + + if (ice.urls.search('stun|stuns') !== -1) { + iceServers.push(getSTUNObj(ice.urls)); + } + + if (ice.urls.search('turn|turns') !== -1) { + iceServers.push(getTURNObj(ice.urls, ice.username, ice.credential)); + } + }); + return iceServers; + } + + var IceServersHandler = (function() { + function getIceServers(connection) { + var iceServers = []; + + iceServers.push(getSTUNObj('stun:stun.l.google.com:19302')); + iceServers.push(getTURNObj('turn:turn.bistri.com:80', 'homeo', 'homeo')); + iceServers.push(getTURNObj('turn:turn.anyfirewall.com:443', 'webrtc', 'webrtc')); + + if (window.RMCExternalIceServers) { + iceServers = iceServers.concat(getExtenralIceFormatted()); + } else if (typeof window.getExternalIceServers !== 'undefined' && window.getExternalIceServers == true) { + connection.iceServers = iceServers; + window.iceServersLoadCallback = function() { + connection.iceServers = connection.iceServers.concat(getExtenralIceFormatted()); + }; + } + + return iceServers; + } + + return { + getIceServers: getIceServers + }; + })(); + + // Last time updated at Fri Jan 08 2016 14:06 + + // gumadapter.js + // https://cdn.webrtc-experiment.com/gumadapter.js + + // getUserMedia hacks from git/webrtc/adapter; + // removed redundant codes + // A-to-Zee, all copyrights goes to: + // https://github.com/webrtc/adapter/blob/master/LICENSE.md + + var getUserMedia = null; + var webrtcDetectedBrowser = null; + var webrtcDetectedVersion = null; + var webrtcMinimumVersion = null; + + var webrtcUtils = window.webrtcUtils || {}; + if (!webrtcUtils.enableLogs) { + webrtcUtils.enableLogs = true; + } + if (!webrtcUtils.log) { + webrtcUtils.log = function() { + if (!webrtcUtils.enableLogs) { + return; + } + + // 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 (!webrtcUtils.extractVersion) { + webrtcUtils.extractVersion = function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); + }; + } + + 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 = stream ? URL.createObjectURL(stream) : null; + } + } + }); + } + + // chrome 50+ supports promises over "play" method + HTMLMediaElement.prototype.nativePlay = HTMLMediaElement.prototype.play; + HTMLMediaElement.prototype.play = function() { + var myself = this; + var promise = myself.nativePlay(); + if (promise) { + promise.then(function() { + // maybe it is Android + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }).catch(function() { + // maybe it is iOS webview + setTimeout(function() { + myself.nativePlay().then(function() { + // skip + }).catch(function() { + alert('Video requires manual action to start the player.'); + }); + }, 1000); + }); + } + }; + + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; + } + + if (typeof window === 'undefined' || !window.navigator) { + webrtcDetectedBrowser = 'not a browser'; + } else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcDetectedBrowser = 'firefox'; + + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); + + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; + + // 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().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + + } else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcDetectedBrowser = 'chrome'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; + + // 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 + }; + } + + // 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.'); + }; + } + + } else if (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); + + // 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); + }); + } + + if (typeof module !== 'undefined') { + module.exports = { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; + } else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + getUserMedia: getUserMedia, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcUtils: webrtcUtils + }; + }); + } + + // getUserMediaHandler.js + + if (typeof webrtcUtils !== 'undefined') { + webrtcUtils.enableLogs = false; + } + + function setStreamType(constraints, stream) { + if (constraints.mandatory && constraints.mandatory.chromeMediaSource) { + stream.isScreen = true; + } else if (constraints.mozMediaSource || constraints.mediaSource) { + stream.isScreen = true; + } else if (constraints.video) { + stream.isVideo = true; + } else if (constraints.audio) { + stream.isAudio = true; + } + } + + var currentUserMediaRequest = { + streams: [], + mutex: false, + queueRequests: [], + remove: function(idInstance) { + this.mutex = false; + + var stream = this.streams[idInstance]; + if (!stream) { + return; + } + + stream = stream.stream; + + var options = stream.currentUserMediaRequestOptions; + + if (this.queueRequests.indexOf(options)) { + delete this.queueRequests[this.queueRequests.indexOf(options)]; + this.queueRequests = removeNullEntries(this.queueRequests); + } + + this.streams[idInstance].stream = null; + delete this.streams[idInstance]; + } + }; + + function getUserMediaHandler(options) { + if (currentUserMediaRequest.mutex === true) { + currentUserMediaRequest.queueRequests.push(options); + return; + } + currentUserMediaRequest.mutex = true; + + // easy way to match + var idInstance = JSON.stringify(options.localMediaConstraints); + + function streaming(stream, returnBack) { + setStreamType(options.localMediaConstraints, stream); + options.onGettingLocalMedia(stream, returnBack); + + stream.addEventListener('ended', function() { + delete currentUserMediaRequest.streams[idInstance]; + + currentUserMediaRequest.mutex = false; + if (currentUserMediaRequest.queueRequests.indexOf(options)) { + delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)]; + currentUserMediaRequest.queueRequests = removeNullEntries(currentUserMediaRequest.queueRequests); + } + }, false); + + currentUserMediaRequest.streams[idInstance] = { + stream: stream + }; + currentUserMediaRequest.mutex = false; + + if (currentUserMediaRequest.queueRequests.length) { + getUserMediaHandler(currentUserMediaRequest.queueRequests.shift()); + } + } + + if (currentUserMediaRequest.streams[idInstance]) { + streaming(currentUserMediaRequest.streams[idInstance].stream, true); + } else { + if (isPluginRTC && window.PluginRTC) { + var mediaElement = document.createElement('video'); + window.PluginRTC.getUserMedia({ + audio: true, + video: true + }, function(stream) { + stream.streamid = stream.id || getRandomString(); + streaming(stream); + }, function(error) {}); + + return; + } + + navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream) { + stream.streamid = stream.streamid || stream.id || getRandomString(); + stream.idInstance = idInstance; + streaming(stream); + }).catch(function(error) { + options.onLocalMediaError(error, options.localMediaConstraints); + }); + } + } + + // StreamsHandler.js + + var StreamsHandler = (function() { + function handleType(type) { + if (!type) { + return; + } + + if (typeof type === 'string' || typeof type === 'undefined') { + return type; + } + + if (type.audio && type.video) { + return null; + } + + if (type.audio) { + return 'audio'; + } + + if (type.video) { + return 'video'; + } + + return; + } + + function setHandlers(stream, syncAction, connection) { + if (!stream || !stream.addEventListener) return; + + if (typeof syncAction == 'undefined' || syncAction == true) { + stream.addEventListener('ended', function() { + StreamsHandler.onSyncNeeded(this.streamid, 'ended'); + }, false); + } + + stream.mute = function(type, isSyncAction) { + type = handleType(type); + + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; + } + + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = false; + connection.streamEvents[stream.streamid].isAudioMuted = true; + }); + } + + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = false; + }); + } + + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type); + } + + connection.streamEvents[stream.streamid].muteType = type || 'both'; + + fireEvent(stream, 'mute', type); + }; + + stream.unmute = function(type, isSyncAction) { + type = handleType(type); + + if (typeof isSyncAction !== 'undefined') { + syncAction = isSyncAction; + } + + graduallyIncreaseVolume(); + + if (typeof type == 'undefined' || type == 'audio') { + stream.getAudioTracks().forEach(function(track) { + track.enabled = true; + connection.streamEvents[stream.streamid].isAudioMuted = false; + }); + } + + if (typeof type == 'undefined' || type == 'video') { + stream.getVideoTracks().forEach(function(track) { + track.enabled = true; + }); + + // make sure that video unmute doesn't affects audio + if (typeof type !== 'undefined' && type == 'video' && connection.streamEvents[stream.streamid].isAudioMuted) { + (function looper(times) { + if (!times) { + times = 0; + } + + times++; + + // check until five-seconds + if (times < 100 && connection.streamEvents[stream.streamid].isAudioMuted) { + stream.mute('audio'); + + setTimeout(function() { + looper(times); + }, 50); + } + })(); + } + } + + if (typeof syncAction == 'undefined' || syncAction == true) { + StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type); + } + + connection.streamEvents[stream.streamid].unmuteType = type || 'both'; + + fireEvent(stream, 'unmute', type); + }; + + function graduallyIncreaseVolume() { + if (!connection.streamEvents[stream.streamid].mediaElement) { + return; + } + + var mediaElement = connection.streamEvents[stream.streamid].mediaElement; + mediaElement.volume = 0; + afterEach(200, 5, function() { + mediaElement.volume += .20; + }); + } + } + + function afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes) { + startedTimes = (startedTimes || 0) + 1; + if (startedTimes >= numberOfTimes) return; + + setTimeout(function() { + callback(); + afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes); + }, setTimeoutInteval); + } + + return { + setHandlers: setHandlers, + onSyncNeeded: function(streamid, action, type) {} + }; + })(); + + // Last time updated at Oct 24, 2015, 08:32:23 + + // Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js + + // Muaz Khan - www.MuazKhan.com + // MIT License - www.WebRTC-Experiment.com/licence + // Documentation - https://github.com/muaz-khan/getScreenId. + + // ______________ + // getScreenId.js + + /* + getScreenId(function (error, sourceId, screen_constraints) { + // error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome' + // sourceId == null || 'string' || 'firefox' + + if(sourceId == 'firefox') { + navigator.mozGetUserMedia(screen_constraints, onSuccess, onFailure); + } + else navigator.webkitGetUserMedia(screen_constraints, onSuccess, onFailure); + }); + */ + + (function() { + window.getScreenId = function(callback) { + // for Firefox: + // sourceId == 'firefox' + // screen_constraints = {...} + if (!!navigator.mozGetUserMedia) { + callback(null, 'firefox', { + video: { + mozMediaSource: 'window', + mediaSource: 'window', + width: 29999, + height: 8640 + } + }); + return; + } + + postMessage(); + + window.addEventListener('message', onIFrameCallback); + + function onIFrameCallback(event) { + if (!event.data) return; + + if (event.data.chromeMediaSourceId) { + if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { + callback('permission-denied'); + } else callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId)); + } + + if (event.data.chromeExtensionStatus) { + callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus)); + } + + // this event listener is no more needed + window.removeEventListener('message', onIFrameCallback); + } + }; + + function getScreenConstraints(error, sourceId) { + var screen_constraints = { + audio: false, + video: { + mandatory: { + chromeMediaSource: error ? 'screen' : 'desktop', + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true + }, + optional: [] + } + }; + + if (sourceId) { + screen_constraints.video.mandatory.chromeMediaSourceId = sourceId; + } + + return screen_constraints; + } + + function postMessage() { + if (!iframe) { + loadIFrame(postMessage); + return; + } + + if (!iframe.isLoaded) { + setTimeout(postMessage, 100); + return; + } + + iframe.contentWindow.postMessage({ + captureSourceId: true + }, '*'); + } + + function loadIFrame(loadCallback) { + if (iframe) { + loadCallback(); + return; + } + + iframe = document.createElement('iframe'); + iframe.onload = function() { + iframe.isLoaded = true; + + loadCallback(); + }; + iframe.src = 'https://www.webrtc-experiment.com/getSourceId/'; // https://wwww.yourdomain.com/getScreenId.html + iframe.style.display = 'none'; + (document.body || document.documentElement).appendChild(iframe); + } + + var iframe; + + // this function is used in v3.0 + window.getScreenConstraints = function(callback) { + loadIFrame(function() { + getScreenId(function(error, sourceId, screen_constraints) { + callback(error, screen_constraints.video); + }); + }); + }; + })(); + + (function() { + if (document.domain.indexOf('webrtc-experiment.com') === -1) { + return; + } + + window.getScreenId = function(callback) { + // for Firefox: + // sourceId == 'firefox' + // screen_constraints = {...} + if (!!navigator.mozGetUserMedia) { + callback(null, 'firefox', { + video: { + mozMediaSource: 'window', + mediaSource: 'window', + width: 29999, + height: 8640 + } + }); + return; + } + + postMessage(); + + window.addEventListener('message', onIFrameCallback); + + function onIFrameCallback(event) { + if (!event.data) return; + + if (event.data.chromeMediaSourceId) { + if (event.data.chromeMediaSourceId === 'PermissionDeniedError') { + callback('permission-denied'); + } else callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId)); + } + + if (event.data.chromeExtensionStatus) { + callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus)); + } + + // this event listener is no more needed + window.removeEventListener('message', onIFrameCallback); + } + }; + + function getScreenConstraints(error, sourceId) { + var screen_constraints = { + audio: false, + video: { + mandatory: { + chromeMediaSource: error ? 'screen' : 'desktop', + maxWidth: 29999, + maxHeight: 8640, + minFrameRate: 30, + maxFrameRate: 128, + minAspectRatio: 1.77, // 2.39 + googLeakyBucket: true + }, + optional: [] + } + }; + + if (sourceId) { + screen_constraints.video.mandatory.chromeMediaSourceId = sourceId; + } + + return screen_constraints; + } + + function postMessage() { + if (!iframe) { + loadIFrame(postMessage); + return; + } + + if (!iframe.isLoaded) { + setTimeout(postMessage, 100); + return; + } + + iframe.contentWindow.postMessage({ + captureSourceId: true + }, '*'); + } + + function loadIFrame(loadCallback) { + if (iframe) { + loadCallback(); + return; + } + + iframe = document.createElement('iframe'); + iframe.onload = function() { + iframe.isLoaded = true; + + loadCallback(); + }; + iframe.src = 'https://www.webrtc-experiment.com/getSourceId/'; // https://wwww.yourdomain.com/getScreenId.html + iframe.style.display = 'none'; + (document.body || document.documentElement).appendChild(iframe); + } + + var iframe; + + // this function is used in v3.0 + window.getScreenConstraints = function(callback) { + loadIFrame(function() { + getScreenId(function(error, sourceId, screen_constraints) { + callback(error, screen_constraints.video); + }); + }); + }; + })(); + + // TextReceiver.js & TextSender.js + + function TextReceiver(connection) { + var content = {}; + + function receive(data, userid, extra) { + // uuid is used to uniquely identify sending instance + var uuid = data.uuid; + if (!content[uuid]) { + content[uuid] = []; + } + + content[uuid].push(data.message); + + if (data.last) { + var message = content[uuid].join(''); + if (data.isobject) { + message = JSON.parse(message); + } + + // latency detection + var receivingTime = new Date().getTime(); + var latency = receivingTime - data.sendingTime; + + var e = { + data: message, + userid: userid, + extra: extra, + latency: latency + }; + + if (connection.autoTranslateText) { + e.original = e.data; + connection.Translator.TranslateText(e.data, function(translatedText) { + e.data = translatedText; + connection.onmessage(e); + }); + } else { + connection.onmessage(e); + } + + delete content[uuid]; + } + } + + return { + receive: receive + }; + } + + // TextSender.js + var TextSender = { + send: function(config) { + var connection = config.connection; + + var channel = config.channel, + remoteUserId = config.remoteUserId, + initialText = config.text, + packetSize = connection.chunkSize || 1000, + textToTransfer = '', + isobject = false; + + if (!isString(initialText)) { + isobject = true; + initialText = JSON.stringify(initialText); + } + + // uuid is used to uniquely identify sending instance + var uuid = getRandomString(); + var sendingTime = new Date().getTime(); + + sendText(initialText); + + function sendText(textMessage, text) { + var data = { + type: 'text', + uuid: uuid, + sendingTime: sendingTime + }; + + if (textMessage) { + text = textMessage; + data.packets = parseInt(text.length / packetSize); + } + + if (text.length > packetSize) { + data.message = text.slice(0, packetSize); + } else { + data.message = text; + data.last = true; + data.isobject = isobject; + } + + channel.send(data, remoteUserId); + + textToTransfer = text.slice(data.message.length); + + if (textToTransfer.length) { + setTimeout(function() { + sendText(null, textToTransfer); + }, connection.chunkInterval || 100); + } + } + } + }; + + // FileProgressBarHandler.js + + var FileProgressBarHandler = (function() { + function handle(connection) { + var progressHelper = {}; + + // www.RTCMultiConnection.org/docs/onFileStart/ + connection.onFileStart = function(file) { + var div = document.createElement('div'); + div.title = file.name; + div.innerHTML = ' '; + + if (file.remoteUserId) { + div.innerHTML += ' (Sharing with:' + file.remoteUserId + ')'; + } + + if (!connection.filesContainer) { + connection.filesContainer = document.body || document.documentElement; + } + + connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); + + if (!file.remoteUserId) { + progressHelper[file.uuid] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid].progress.max = file.maxChunks; + return; + } + + if (!progressHelper[file.uuid]) { + progressHelper[file.uuid] = {}; + } + + progressHelper[file.uuid][file.remoteUserId] = { + div: div, + progress: div.querySelector('progress'), + label: div.querySelector('label') + }; + progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; + }; + + // www.RTCMultiConnection.org/docs/onFileProgress/ + connection.onFileProgress = function(chunk) { + var helper = progressHelper[chunk.uuid]; + if (!helper) { + return; + } + if (chunk.remoteUserId) { + helper = progressHelper[chunk.uuid][chunk.remoteUserId]; + if (!helper) { + return; + } + } + + helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; + updateLabel(helper.progress, helper.label); + }; + + // www.RTCMultiConnection.org/docs/onFileEnd/ + connection.onFileEnd = function(file) { + var helper = progressHelper[file.uuid]; + if (!helper) { + console.error('No such progress-helper element exists.', file); + return; + } + + if (file.remoteUserId) { + helper = progressHelper[file.uuid][file.remoteUserId]; + if (!helper) { + return; + } + } + + var div = helper.div; + if (file.type.indexOf('image') != -1) { + div.innerHTML = 'Download ' + file.name + '
'; + } else { + div.innerHTML = 'Download ' + file.name + '
'; + } + }; + + function updateLabel(progress, label) { + if (progress.position === -1) { + return; + } + + var position = +progress.position.toFixed(2).split('.')[1] || 100; + label.innerHTML = position + '%'; + } + } + + return { + handle: handle + }; + })(); + + // TranslationHandler.js + + var TranslationHandler = (function() { + function handle(connection) { + connection.autoTranslateText = false; + connection.language = 'en'; + connection.googKey = 'AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE'; + + // www.RTCMultiConnection.org/docs/Translator/ + connection.Translator = { + TranslateText: function(text, callback) { + // if(location.protocol === 'https:') return callback(text); + + var newScript = document.createElement('script'); + newScript.type = 'text/javascript'; + + var sourceText = encodeURIComponent(text); // escape + + var randomNumber = 'method' + connection.token(); + window[randomNumber] = function(response) { + if (response.data && response.data.translations[0] && callback) { + callback(response.data.translations[0].translatedText); + } + + if (response.error && response.error.message === 'Daily Limit Exceeded') { + warn('Text translation failed. Error message: "Daily Limit Exceeded."'); + + // returning original text + callback(text); + } + }; + + var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText; + newScript.src = source; + document.getElementsByTagName('head')[0].appendChild(newScript); + } + }; + } + + return { + handle: handle + }; + })(); + + window.RTCMultiConnection = RTCMultiConnection; +})(); diff --git a/RTCMultiConnection/dist/rmc3.min.js b/RTCMultiConnection/dist/rmc3.min.js new file mode 100644 index 00000000..dc89fe7f --- /dev/null +++ b/RTCMultiConnection/dist/rmc3.min.js @@ -0,0 +1,6 @@ +// Last time updated: 2016-03-17 11:15:55 AM UTC + +"use strict";!function(){function RTCMultiConnection(roomid,forceOptions){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(socket)return void(connectCallback&&connectCallback(socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}socket=new SocketConnection(connection,function(s){socket=s,connectCallback&&connectCallback(socket)})}function beforeUnload(shiftModerationControlOnLeave,dontCloseSocket){connection.closeBeforeUnload&&(connection.isInitiator===!0&&connection.dontMakeMeModerator(),connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.broadcasters=[],connection.isInitiator=!1)}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&stream.getAudioTracks().forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&stream.getVideoTracks().forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}function keepNextBroadcasterOnServer(){if(connection.isInitiator&&!connection.session.oneway&&!connection.session.broadcast&&"many-to-many"===connection.direction){var firstBroadcaster=connection.broadcasters[0],otherBroadcasters=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==firstBroadcaster&&otherBroadcasters.push(broadcaster)}),connection.autoCloseEntireSession||connection.shiftModerationControl(firstBroadcaster,otherBroadcasters,!0)}}forceOptions=forceOptions||{};var connection=this;connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection);mPeer.onGettingLocalMedia=function(stream){stream.type="local",connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,-1===connection.attachStreams.indexOf(stream)&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream),isAudioMuted:!0},setHarkEvents(connection,connection.streamEvents[stream.streamid]),setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onGettingRemoteMedia=function(stream,remoteUserId){stream.type="remote",connection.setStreamEndHandler(stream,"remote-stream"),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},mediaElement:mediaElement,streamid:stream.streamid,blobURL:mediaElement.src||URL.createObjectURL(stream)},setMuteHandlers(connection,connection.streamEvents[stream.streamid]),connection.onstream(connection.streamEvents[stream.streamid])},connection)},mPeer.onRemovingRemoteMedia=function(stream,remoteUserId){var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,type:"remote",userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},streamid:stream.streamid,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid]},mPeer.onNegotiationNeeded=function(message,remoteUserId,callback){connectSocket(function(){socket.emit(connection.socketMessageEvent,"password"in message?message:{remoteUserId:message.remoteUserId||remoteUserId,message:message,sender:connection.userid},callback||function(){})})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){socket&&socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.broadcasters=[],connection.socketOptions={transport:"polling"};var socket;connection.openOrJoin=function(localUserid,password){connection.checkPresence(localUserid,function(isRoomExists,roomid){if("function"==typeof password&&(password(isRoomExists,roomid),password=null),isRoomExists){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,isDataOnly=isData(connection.session);remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo};var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:password||!1};return void mPeer.onNegotiationNeeded(connectionDescription)}connection.userid;connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",socket.emit("changed-uuid",connection.userid),password&&socket.emit("set-password",password),connection.isInitiator=!0,isData(connection.session)||connection.captureUserMedia()})},connection.open=function(localUserid,isPublicModerator){connection.userid;return connection.userid=connection.sessionid=localUserid||connection.sessionid,connection.userid+="",connection.isInitiator=!0,connectSocket(function(){socket.emit("changed-uuid",connection.userid),1==isPublicModerator&&connection.becomePublicModerator()}),isData(connection.session)?void("function"==typeof isPublicModerator&&isPublicModerator()):void connection.captureUserMedia("function"==typeof isPublicModerator?isPublicModerator:null)},connection.becomePublicModerator=function(){connection.isInitiator&&socket.emit("become-a-public-moderator")},connection.dontMakeMeModerator=function(){socket.emit("dont-make-me-moderator")},connection.deletePeer=function(remoteUserId){if(remoteUserId){if(connection.onleave({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}}),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}if(-1!==connection.broadcasters.indexOf(remoteUserId)){var newArray=[];connection.broadcasters.forEach(function(broadcaster){broadcaster!==remoteUserId&&newArray.push(broadcaster)}),connection.broadcasters=newArray,keepNextBroadcasterOnServer()}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),connectionDescription&&connectionDescription.remoteUserId&&(connection.join(connectionDescription.remoteUserId),connection.onReConnecting({userid:connectionDescription.remoteUserId,extra:extra}))}},connection.join=connection.connect=function(remoteUserId,options){connection.sessionid=(remoteUserId?remoteUserId.sessionid||remoteUserId.remoteUserId||remoteUserId:!1)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){var session=remoteUserId?remoteUserId.session||connection.session:connection.session;isOneWay=!!session.oneway,isDataOnly=isData(session),remotePeerSdpConstraints={OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},localPeerSdpConstraints={OfferToReceiveAudio:isOneWay?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:isOneWay?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo}}options=options||{},"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid,password:!1};return connectSocket(function(){connection.peers[connection.sessionid]||mPeer.onNegotiationNeeded(connectionDescription)}),connectionDescription},connection.connectWithAllParticipants=function(remoteUserId){mPeer.onNegotiationNeeded("connectWithAllParticipants",remoteUserId||connection.sessionid)},connection.removeFromBroadcastersList=function(remoteUserId){mPeer.onNegotiationNeeded("removeFromBroadcastersList",remoteUserId||connection.sessionid),connection.peers.getAllParticipants(remoteUserId||connection.sessionid).forEach(function(participant){mPeer.onNegotiationNeeded("dropPeerConnection",participant),connection.deletePeer(participant)}),connection.attachStreams.forEach(function(stream){stream.stop()})},connection.getUserMedia=connection.captureUserMedia=function(callback,session){function invokeGetUserMedia(localMediaConstraints,getUserMedia_callback){var isScreen=!1;localMediaConstraints&&(isScreen=localMediaConstraints.isScreen,delete localMediaConstraints.isScreen),getUserMediaHandler({onGettingLocalMedia:function(stream){return stream.isAudio=stream.isVideo=stream.isScreen=!1,isScreen?stream.isScreen=!0:session.audio&&session.video?stream.isVideo=!0:session.audio&&(stream.isAudio=!0),mPeer.onGettingLocalMedia(stream),getUserMedia_callback?getUserMedia_callback():void(callback&&callback(stream))},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,getUserMedia_callback)):void(callback&&callback())},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return session=session||connection.session,connection.dontCaptureUserMedia||isData(session)?void(callback&&callback()):void((session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){if(error)throw error;invokeGetUserMedia({video:screen_constraints,isScreen:!0},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.closeBeforeUnload=!0,window.addEventListener("beforeunload",beforeUnload,!1),connection.userid=getRandomString(),connection.changeUserId=function(newUserId,callback){connection.userid=newUserId||getRandomString(),socket.emit("changed-uuid",connection.userid,callback||function(){})},connection.extra={},connection.attachStreams=[],connection.removeStreams=[],connection.session={audio:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:512,audio:128,video:512},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return isMobileDevice||isFirefox?sdp:(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen),sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024}),sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3}),"VP9"===connection.codecs.video&&(sdp=CodecsHandler.preferVP9(sdp)),"H264"===connection.codecs.video&&(sdp=CodecsHandler.removeVPX(sdp)),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),sdp)},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]},video:{mandatory:{},optional:[{bandwidth:8*connection.bandwidth.audio*1024||1048576},{googLeakyBucket:!0},{facingMode:"user"}]}},isFirefox&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if(isFirefox)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),connection.mediaConstraints.audio.optional||(connection.mediaConstraints.audio.optional=[]);var optional=[{sourceId:lastAudioDevice.id}];connection.mediaConstraints.audio.optional=optional.concat(connection.mediaConstraints.audio.optional)}if(lastVideoDevice){if(isFirefox)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),connection.mediaConstraints.video.optional||(connection.mediaConstraints.video.optional=[]);var optional=[{sourceId:lastVideoDevice.id}];connection.mediaConstraints.video.optional=optional.concat(connection.mediaConstraints.video.optional)}}),connection.sdpConstraints={mandatory:{OfferToReceiveAudio:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){connection.enableLogs&&console.debug("data-message",event.userid,event.data)},connection.send=function(data,remoteUserId){connection.peers.send(data,remoteUserId)},connection.close=connection.disconnect=connection.leave=function(){beforeUnload(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&console.info("Entire session is closed: ",event.sessionid,event.extra)},connection.onstream=function(e){var parentNode=connection.videosContainer;parentNode.insertBefore(e.mediaElement,parentNode.firstChild),e.mediaElement.play(),setTimeout(function(){e.mediaElement.play()},5e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?void(-1===connection.removeStreams.indexOf(stream)&&(connection.removeStreams.push(stream),connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)}))):void console.warn("No such stream exists.",streamid)},connection.addStream=function(session,remoteUserId){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints?localMediaConstraints.video:connection.mediaConstraints;return videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=stream.getVideoTracks().length,stream.isAudio=!stream.isVideo&&stream.getAudioTracks().length),mPeer.onGettingLocalMedia(stream),session.streamCallback&&session.streamCallback(stream),callback?callback():void connection.renegotiate(remoteUserId)},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,callback)):callback?callback():void connection.renegotiate(remoteUserId)},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}return session.getAudioTracks?(-1===connection.attachStreams.indexOf(session)&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((!session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia()))},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function invokeGetUserMedia(localMediaConstraints,callback){getUserMediaHandler({onGettingLocalMedia:function(stream){return mPeer.onGettingLocalMedia(stream),callback?callback():void connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)},onLocalMediaError:function(error,constraints){return mPeer.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,callback)):void(callback&&callback())},localMediaConstraints:localMediaConstraints||{audio:session.audio?connection.mediaConstraints.audio:!1,video:session.video?connection.mediaConstraints.video:!1}})}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return session.getVideoTracks().length&&replaceTrack(session.getVideoTracks()[0],remoteUserId,!0),void(session.getAudioTracks().length&&replaceTrack(session.getAudioTracks()[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(!session.audio||session.video||session.screen)&&(session.screen?connection.getScreenConstraints(function(error,screen_constraints){return error?alert(error):void invokeGetUserMedia({video:screen_constraints},session.audio||session.video?invokeGetUserMedia:!1)}):(session.audio||session.video)&&invokeGetUserMedia())},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){stream&&stream.addEventListener&&(isRemote=!!isRemote,stream.alreadySetEndHandler||(stream.alreadySetEndHandler=!0,stream.addEventListener("ended",function(){stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),isRemote||(delete connection.attachStreams[connection.attachStreams.indexOf(stream)],-1===connection.removeStreams.indexOf(stream)&&connection.removeStreams.push(stream),connection.attachStreams=removeNullEntries(connection.attachStreams),connection.removeStreams=removeNullEntries(connection.removeStreams));var streamEvent=connection.streamEvents[stream.streamid];streamEvent||(streamEvent={stream:stream,streamid:stream.streamid,type:isRemote?"remote":"local",userid:connection.userid,extra:connection.extra,mediaElement:connection.streamEvents[stream.streamid]?connection.streamEvents[stream.streamid].mediaElement:null}),(streamEvent.userid!==connection.userid||"remote"!==streamEvent.type)&&(connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)))},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.addNewBroadcaster=function(broadcasterId,userPreferences){connection.broadcasters.length&&setTimeout(function(){mPeer.connectNewParticipantWithAllBroadcasters(broadcasterId,userPreferences,connection.broadcasters.join("|-,-|"))},1e4),connection.session.oneway||connection.session.broadcast||"many-to-many"!==connection.direction||-1!==connection.broadcasters.indexOf(broadcasterId)||(connection.broadcasters.push(broadcasterId),keepNextBroadcasterOnServer())},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},connection.onShiftedModerationControl=function(sender,existingBroadcasters){connection.acceptModerationControl(sender,existingBroadcasters)},connection.acceptModerationControl=function(sender,existingBroadcasters){connection.isInitiator=!0,connection.broadcasters=existingBroadcasters,connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({changedUUID:sender,oldUUID:connection.userid,newUUID:sender},participant)}),connection.userid=sender,socket.emit("changed-uuid",connection.userid)},connection.shiftModerationControl=function(remoteUserId,existingBroadcasters,firedOnLeave){mPeer.onNegotiationNeeded({shiftedModerationControl:!0,broadcasters:existingBroadcasters,firedOnLeave:!!firedOnLeave},remoteUserId)},"undefined"!=typeof StreamsHandler&&(connection.StreamsHandler=StreamsHandler),connection.onleave=function(userid){},connection.invokeSelectFileDialog=function(callback){var selector=new FileSelector;selector.selectSingleFile(callback)},connection.getPublicModerators=function(userIdStartsWith,callback){"function"==typeof userIdStartsWith&&(callback=userIdStartsWith),connectSocket(function(socket){socket.emit("get-public-moderators","string"==typeof userIdStartsWith?userIdStartsWith:"",callback)})},connection.onmute=function(e){e.mediaElement&&("both"===e.muteType||"video"===e.muteType?(e.mediaElement.src=null,e.mediaElement.pause(),e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"):"audio"===e.muteType&&(e.mediaElement.muted=!0))},connection.onunmute=function(e){e.mediaElement&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.src=URL.createObjectURL(e.stream),e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.onJoinWithPassword=function(remoteUserId){console.warn(remoteUserId,"is password protected. Please join with password.")},connection.onInvalidPassword=function(remoteUserId,oldPassword){console.warn(remoteUserId,"is password protected. Please join with valid password. Your old password",oldPassword,"is wrong.")},connection.onPasswordMaxTriesOver=function(remoteUserId){console.warn(remoteUserId,"is password protected. Your max password tries exceeded the limit.")},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){socket&&("undefined"!=typeof socket.disconnect&&socket.disconnect(),socket=null)},connection.getSocket=function(callback){return socket?callback&&callback(socket):connectSocket(callback),socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){if(!options){var firstStream;for(var str in connection.streamEvents)-1!==skipStreams.indexOf(str)||firstStream||(firstStream=connection.streamEvents[str]);return firstStream}},selectAll:function(){}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=65e3,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(remoteUserId,callback){mPeer.onNegotiationNeeded({detectPresence:!0,userid:(remoteUserId||connection.sessionid)+""},"system",callback)},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.getScreenConstraints=function(callback){getScreenConstraints(function(error,screen_constraints){error||(screen_constraints=connection.modifyScreenConstraints(screen_constraints),callback(error,screen_constraints))})},connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&-1!==state.iceConnectionState.search(/closed|failed/gi)&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=-1!==navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g),connection.isLowBandwidth)){if(connection.bandwidth={audio:30,video:30,screen:30},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt); +}),connection.mediaConstraints.audio.optional=newArray}if(connection.mediaConstraints.video&&connection.mediaConstraints.video.optional.length){var newArray=[];connection.mediaConstraints.video.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId){if(!remoteUserId)throw"remoteUserId is required.";return connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",yourNewUserId),connection.join(useridAlreadyTaken)},connection.trickleIce=!0}function SocketConnection(connection,connectCallback){var parameters="";parameters+="?userid="+connection.userid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2));var socket;try{socket=io((connection.socketURL||"/")+parameters)}catch(e){socket=io.connect((connection.socketURL||"/")+parameters,connection.socketOptions)}var mPeer=connection.multiPeersHandler;return socket.on("extra-data-updated",function(remoteUserId,extra){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}))}),socket.on(connection.socketMessageEvent,function(message){if(message.remoteUserId==connection.userid){if(connection.peers[message.sender]&&connection.peers[message.sender].extra!=message.extra&&(connection.peers[message.sender].extra=message.extra,connection.onExtraDataUpdated({userid:message.sender,extra:message.extra})),message.message.streamSyncNeeded&&connection.peers[message.sender]){var stream=connection.streamEvents[message.message.streamid];if(!stream||!stream.stream)return;var action=message.message.action;if("ended"===action||"stream-removed"===action)return void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void stream.stream[action](type)}if("connectWithAllParticipants"===message.message)return-1===connection.broadcasters.indexOf(message.sender)&&connection.broadcasters.push(message.sender),void mPeer.onNegotiationNeeded({allParticipants:connection.getAllParticipants(message.sender)},message.sender);if("removeFromBroadcastersList"===message.message)return void(-1!==connection.broadcasters.indexOf(message.sender)&&(delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)],connection.broadcasters=removeNullEntries(connection.broadcasters)));if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return-1===message.message.allParticipants.indexOf(message.sender)&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](participant,{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void mPeer.createNewPeer(message.message.newParticipant,message.message.userPreferences||{localPeerSdpConstraints:{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if((message.message.readyForOffer||message.message.addMeAsBroadcaster)&&connection.addNewBroadcaster(message.sender),message.message.newParticipationRequest&&message.sender!==connection.userid){connection.peers[message.sender]&&connection.deletePeer(message.sender);var userPreferences={extra:message.extra||{},localPeerSdpConstraints:message.message.remotePeerSdpConstraints||{OfferToReceiveAudio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.sdpConstraints.mandatory.OfferToReceiveVideo},remotePeerSdpConstraints:message.message.localPeerSdpConstraints||{OfferToReceiveAudio:connection.session.oneway?!!connection.session.audio:connection.sdpConstraints.mandatory.OfferToReceiveAudio,OfferToReceiveVideo:connection.session.oneway?!!connection.session.video||!!connection.session.screen:connection.sdpConstraints.mandatory.OfferToReceiveVideo},isOneWay:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){("undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction)&&connection.addNewBroadcaster(message.sender,userPreferences),(connection.session.oneway||"one-way"===connection.direction||isData(connection.session))&&connection.addNewBroadcaster(message.sender,userPreferences)}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.shiftedModerationControl?void connection.onShiftedModerationControl(message.sender,message.message.broadcasters):(message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender))}}),socket.on("user-left",function(userid){onUserLeft(userid),connection.onUserStatusChanged({userid:userid,status:"offline",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}}),connection.onleave({userid:userid,extra:{}})}),socket.on("connect",function(){connection.enableLogs&&console.info("socket.io connection is opened."),socket.emit("extra-data-updated",connection.extra),connectCallback&&connectCallback(socket)}),socket.on("disconnect",function(){connection.enableLogs&&(console.info("socket.io connection is closed"),console.warn("socket.io reconnecting"))}),socket.on("join-with-password",function(remoteUserId){connection.onJoinWithPassword(remoteUserId)}),socket.on("invalid-password",function(remoteUserId,oldPassword){connection.onInvalidPassword(remoteUserId,oldPassword)}),socket.on("password-max-tries-over",function(remoteUserId){connection.onPasswordMaxTriesOver(remoteUserId)}),socket.on("user-disconnected",function(remoteUserId){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),socket.on("user-connected",function(userid){userid!==connection.userid&&connection.onUserStatusChanged({userid:userid,status:"online",extra:connection.peers[userid]?connection.peers[userid].extra||{}:{}})}),socket.on("closed-entire-session",function(sessionid,extra){connection.leave(),connection.onEntireSessionClosed({sessionid:sessionid,userid:sessionid,extra:extra})}),socket.on("userid-already-taken",function(useridAlreadyTaken,yourNewUserId){connection.isInitiator=!1,connection.userid=yourNewUserId,connection.onUserIdAlreadyTaken(useridAlreadyTaken,yourNewUserId)}),socket.on("logs",function(log){connection.enableLogs&&console.debug("server-logs",log)}),socket}function MultiPeers(connection){function invokeGetUserMedia(mediaConstraints,message,remoteUserId){getUserMediaHandler({onGettingLocalMedia:function(localStream){self.onGettingLocalMedia(localStream);var streamsToShare={};connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},onLocalMediaError:function(error,constraints){return self.onLocalMediaError(error,constraints),constraints.audio&&constraints.video&&-1!==(error.name||"").toString().indexOf("DevicesNotFound")?(constraints.video=!1,void invokeGetUserMedia(constraints,message,remoteUserId)):void self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)},localMediaConstraints:mediaConstraints})}function initFileBufferReader(){fbr=new FileBufferReader,fbr.onProgress=function(chunk){connection.onFileProgress(chunk)},fbr.onBegin=function(file){connection.onFileStart(file)},fbr.onEnd=function(file){connection.onFileEnd(file)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)-1==skipPeers.indexOf(peer)&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)-1==skipPeers.indexOf(peer)&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)-1==skipPeers.indexOf(peer)&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type))return void self.shareFile(data,remoteUserId);if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return void remoteUser.channels.forEach(function(channel){channel.send(data)})}this.getAllParticipants().forEach(function(participant){that[participant].channels.forEach(function(channel){channel.send(data)})})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{streamsToShare:userPreferences.streamsToShare||{},rtcMultiConnection:connection,connectionDescription:userPreferences.connectionDescription,userid:remoteUserId,localPeerSdpConstraints:userPreferences.localPeerSdpConstraints,remotePeerSdpConstraints:userPreferences.remotePeerSdpConstraints,dontGetRemoteStream:!!userPreferences.dontGetRemoteStream,dontAttachLocalStream:!!userPreferences.dontAttachLocalStream,renegotiatingPeer:!!userPreferences.renegotiatingPeer,peerRef:userPreferences.peerRef,onLocalSdp:function(localSdp){self.onNegotiationNeeded(localSdp,remoteUserId)},onLocalCandidate:function(localCandidate){localCandidate=OnIceCandidateHandler.processCandidates(connection,localCandidate),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void fbr.getNextChunk(message.uuid,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):void fbr.addChunk(message,function(promptNextChunk){connection.peers[remoteUserId].peer.channel.send(promptNextChunk)})},onDataChannelError:function(error){self.onDataChannelError(error,remoteUserId)},onDataChannelOpened:function(channel){self.onDataChannelOpened(channel,remoteUserId)},onDataChannelClosed:function(event){self.onDataChannelClosed(event,remoteUserId)},onRemoteStream:function(stream){if(connection.peers[remoteUserId].streams.push(stream),isPluginRTC&&window.PluginRTC){var mediaElement=document.createElement("video"),body=connection.videosContainer;return body.insertBefore(mediaElement,body.firstChild),void setTimeout(function(){window.PluginRTC.attachMediaStream(mediaElement,stream)},3e3)}self.onGettingRemoteMedia(stream,remoteUserId)},onRemoteStreamRemoved:function(stream){self.onRemovingRemoteMedia(stream,remoteUserId)},onPeerStateChanged:function(states){self.onPeerStateChanged(states),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),-1!==states.iceConnectionState.search(/closed|failed/gi)&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);userPreferences=connection.setUserPreferences(userPreferences,remoteUserId);var localConfig=this.getLocalConfig(null,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)}},this.createAnsweringPeer=function(remoteSdp,remoteUserId,userPreferences){userPreferences=connection.setUserPreferences(userPreferences||{},remoteUserId);var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.renegotiatePeer=function(remoteUserId,userPreferences,remoteSdp){if(!connection.peers[remoteUserId])return void(connection.enableLogs&&console.error("This peer ("+remoteUserId+") does not exists. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,userPreferences.peerRef=connection.peers[remoteUserId].peer;var localConfig=this.getLocalConfig(remoteSdp,remoteUserId,userPreferences);connection.peers[remoteUserId]=new PeerInitiator(localConfig)},this.replaceTrack=function(track,remoteUserId,isVideoTrack){if(!connection.peers[remoteUserId])throw"This peer ("+remoteUserId+") does not exists.";var peer=connection.peers[remoteUserId].peer;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&rtpSender.track instanceof VideoStreamTrack&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),!isVideoTrack&&rtpSender.track instanceof AudioStreamTrack&&(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){if(connection.attachStreams.length||connection.dontCaptureUserMedia){var streamsToShare={};return connection.attachStreams.forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),message.userPreferences.streamsToShare=streamsToShare,void self.onNegotiationNeeded({readyForOffer:!0,userPreferences:message.userPreferences},remoteUserId)}var localMediaConstraints={},userPreferences=message.userPreferences;userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video),invokeGetUserMedia(localMediaConstraints,message,remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.connectNewParticipantWithAllBroadcasters=function(newParticipantId,userPreferences,broadcastersList){if(broadcastersList=broadcastersList.split("|-,-|"),broadcastersList.length){var firstBroadcaster=broadcastersList[0];self.onNegotiationNeeded({newParticipant:newParticipantId,userPreferences:userPreferences||!1},firstBroadcaster),delete broadcastersList[0];var array=[];broadcastersList.forEach(function(broadcaster){broadcaster&&array.push(broadcaster)}),setTimeout(function(){self.connectNewParticipantWithAllBroadcasters(newParticipantId,userPreferences,array.join("|-,-|"))},1e4)}},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)};var fbr;this.shareFile=function(file,remoteUserId){if(!connection.enableFileSharing)throw'"connection.enableFileSharing" is false.';initFileBufferReader(),fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();remoteUserId&&(arrayOfUsers=[remoteUserId]),arrayOfUsers.forEach(function(participant){fbr.getNextChunk(uuid,function(nextChunk){connection.peers[participant].channels.forEach(function(channel){channel.send(nextChunk)})},participant)})},{userid:connection.userid,chunkSize:isFirefox?15e3:connection.chunkSize||0})};var textReceiver=new TextReceiver(connection);this.onDataChannelMessage=function(message,remoteUserId){textReceiver.receive(JSON.parse(message),remoteUserId,connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{})},this.onDataChannelClosed=function(event,remoteUserId){event.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onclose(event)},this.onDataChannelError=function(error,remoteUserId){error.userid=remoteUserId,event.extra=connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},connection.onerror(error)},this.onDataChannelOpened=function(channel,remoteUserId){connection.peers[remoteUserId].channels.length||(connection.peers[remoteUserId].channels.push(channel),connection.onopen({userid:remoteUserId,extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:{},channel:channel}))},this.onPeerStateChanged=function(state){connection.onPeerStateChanged(state)},this.onNegotiationStarted=function(remoteUserId,states){},this.onNegotiationCompleted=function(remoteUserId,states){},this.getRemoteStreams=function(remoteUserId){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]},this.isPluginRTC=connection.isPluginRTC=isPluginRTC}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[event.target.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&-1===navigator.userAgent.indexOf("Safari")){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;l>i;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function getRMCMediaElement(stream,callback,connection){var isAudioOnly=!1;stream.getVideoTracks&&!stream.getVideoTracks().length&&(isAudioOnly=!0);var mediaElement=document.createElement(isAudioOnly?"audio":"video");return isPluginRTC&&window.PluginRTC?(connection.videosContainer.insertBefore(mediaElement,connection.videosContainer.firstChild),void setTimeout(function(){window.PluginRTC.attachMediaStream(mediaElement,stream),callback(mediaElement)},1e3)):(mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:window.URL.createObjectURL(stream),mediaElement.controls=!0,isFirefox&&mediaElement.addEventListener("ended",function(){if(currentUserMediaRequest.remove(stream.idInstance),"local"===stream.type){StreamsHandler.onSyncNeeded(stream.streamid,"ended"),connection.attachStreams.forEach(function(aStream,idx){stream.streamid===aStream.streamid&&delete connection.attachStreams[idx]});var newStreamsArray=[];connection.attachStreams.forEach(function(aStream){aStream&&newStreamsArray.push(aStream)}),connection.attachStreams=newStreamsArray;var streamEvent=connection.streamEvents[stream.streamid];if(streamEvent)return void connection.onstreamended(streamEvent);this.parentNode&&this.parentNode.removeChild(this)}},!1),mediaElement.play(),void callback(mediaElement))}function listenEventHandler(eventName,eventHandler){window.removeEventListener(eventName,eventHandler),window.addEventListener(eventName,eventHandler,!1)}function removeNullEntries(array){var newArray=[];return array.forEach(function(item){item&&newArray.push(item)}),newArray}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function setCordovaAPIs(){if("iOS"===DetectRTC.osName&&"undefined"!=typeof cordova&&"undefined"!=typeof cordova.plugins&&"undefined"!=typeof cordova.plugins.iosrtc){var iosrtc=cordova.plugins.iosrtc;window.webkitRTCPeerConnection=iosrtc.RTCPeerConnection,window.RTCSessionDescription=iosrtc.RTCSessionDescription,window.RTCIceCandidate=iosrtc.RTCIceCandidate,window.MediaStream=iosrtc.MediaStream,window.MediaStreamTrack=iosrtc.MediaStreamTrack,navigator.getUserMedia=navigator.webkitGetUserMedia=iosrtc.getUserMedia,iosrtc.debug.enable("iosrtc*"),iosrtc.registerGlobals()}}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function PeerInitiator(config){function createDataChannel(){if(!isOfferer)return void(peer.ondatachannel=function(event){var channel=event.channel;setChannelEvents(channel)});var channel=peer.createDataChannel("RTCDataChannel",{});setChannelEvents(channel)}function setChannelEvents(channel){channel.binaryType="arraybuffer",channel.onmessage=function(event){config.onDataChannelMessage(event.data)},channel.onopen=function(){config.onDataChannelOpened(channel)},channel.onerror=function(error){config.onDataChannelError(error)},channel.onclose=function(event){config.onDataChannelClosed(event)},channel.internalSend=channel.send,channel.send=function(data){"open"===channel.readyState&&channel.internalSend(data)},peer.channel=channel}if(!RTCPeerConnection)throw"WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.";var connection=config.rtcMultiConnection;this.extra=config.remoteSdp?config.remoteSdp.extra:connection.extra,this.userid=config.userid,this.streams=[],this.channels=[],this.connectionDescription=config.connectionDescription;var that=this;config.remoteSdp&&(this.connectionDescription=config.remoteSdp.connectionDescription);var allRemoteStreams={};defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!0,OfferToReceiveVideo:!0});var peer,renegotiatingPeer=!!config.renegotiatingPeer;config.remoteSdp&&(renegotiatingPeer=!!config.remoteSdp.renegotiatingPeer);var localStreams=[];connection.attachStreams.forEach(function(stream){stream&&localStreams.push(stream)}),renegotiatingPeer?(peer=config.peerRef,peer.getLocalStreams().forEach(function(stream){localStreams.forEach(function(localStream,index){stream==localStream&&delete localStreams[index]}),connection.removeStreams.forEach(function(streamToRemove,index){stream===streamToRemove&&(stream=connection.beforeRemovingStream(stream),stream&&peer.removeStream&&peer.removeStream(stream),localStreams.forEach(function(localStream,index){streamToRemove==localStream&&delete localStreams[index]}))})})):peer=new RTCPeerConnection(navigator.onLine?{iceServers:connection.iceServers,iceTransports:"all"}:null,window.PluginRTC?null:connection.optionalArgument),"Firefox"===connection.DetectRTC.browser.name&&(peer.removeStream=function(stream){stream.mute(),connection.StreamsHandler.onSyncNeeded(stream.streamid,"stream-removed")}),peer.onicecandidate=function(event){if(event.candidate)connection.trickleIce&&config.onLocalCandidate({candidate:event.candidate.candidate,sdpMid:event.candidate.sdpMid,sdpMLineIndex:event.candidate.sdpMLineIndex});else if(!connection.trickleIce){var localSdp=peer.localDescription;config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})}};var isFirefoxOffered=!isFirefox;config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.isFirefoxOffered&&(isFirefoxOffered=!0),localStreams.forEach(function(localStream){config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream||config.dontAttachLocalStream||(localStream=connection.beforeAddingStream(localStream),localStream&&peer.addStream(localStream))}),peer.oniceconnectionstatechange=peer.onsignalingstatechange=function(){var extra=that.extra;connection.peers[that.userid]&&(extra=connection.peers[that.userid].extra||extra),peer&&config.onPeerStateChanged({iceConnectionState:peer.iceConnectionState,iceGatheringState:peer.iceGatheringState,signalingState:peer.signalingState,extra:extra,userid:that.userid})};var sdpConstraints={OfferToReceiveAudio:!!localStreams.length,OfferToReceiveVideo:!!localStreams.length};config.localPeerSdpConstraints&&(sdpConstraints=config.localPeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),peer.onaddstream=function(event){var streamsToShare={};config.remoteSdp&&config.remoteSdp.streamsToShare?streamsToShare=config.remoteSdp.streamsToShare:config.streamsToShare&&(streamsToShare=config.streamsToShare);var streamToShare=streamsToShare[event.stream.id];streamToShare&&(event.stream.isAudio=streamToShare.isAudio,event.stream.isVideo=streamToShare.isVideo,event.stream.isScreen=streamToShare.isScreen),event.stream.streamid=event.stream.id,event.stream.stop||(event.stream.stop=function(){isFirefox&&fireEvent(this,"ended")}),allRemoteStreams[event.stream.id]=event.stream,config.onRemoteStream(event.stream)},peer.onremovestream=function(event){event.stream.streamid=event.stream.id,allRemoteStreams[event.stream.id]&&delete allRemoteStreams[event.stream.id],config.onRemoteStreamRemoved(event.stream)},this.addRemoteCandidate=function(remoteCandidate){peer.addIceCandidate(new RTCIceCandidate(remoteCandidate))},this.addRemoteSdp=function(remoteSdp){remoteSdp.sdp=connection.processSdp(remoteSdp.sdp),peer.setRemoteDescription(new RTCSessionDescription(remoteSdp),function(){},function(error){connection.enableLogs&&console.error(JSON.stringify(error,null," "),"\n",remoteSdp.type,remoteSdp.sdp)})};var isOfferer=!0;config.remoteSdp&&(isOfferer=!1),connection.session.data===!0&&createDataChannel(),config.remoteSdp&&(config.remoteSdp.remotePeerSdpConstraints&&(sdpConstraints=config.remoteSdp.remotePeerSdpConstraints),defaults.sdpConstraints=setSdpConstraints(sdpConstraints),this.addRemoteSdp(config.remoteSdp)),("two-way"==connection.session.audio||"two-way"==connection.session.video||"two-way"==connection.session.screen)&&(defaults.sdpConstraints=setSdpConstraints({OfferToReceiveAudio:"two-way"==connection.session.audio||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio,OfferToReceiveVideo:"two-way"==connection.session.video||"two-way"==connection.session.screen||config.remoteSdp&&config.remoteSdp.remotePeerSdpConstraints&&config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio}));var streamsToShare={};peer.getLocalStreams().forEach(function(stream){streamsToShare[stream.streamid]={isAudio:!!stream.isAudio,isVideo:!!stream.isVideo,isScreen:!!stream.isScreen}}),peer[isOfferer?"createOffer":"createAnswer"](function(localSdp){localSdp.sdp=connection.processSdp(localSdp.sdp),peer.setLocalDescription(localSdp),connection.trickleIce&&config.onLocalSdp({type:localSdp.type,sdp:localSdp.sdp,remotePeerSdpConstraints:config.remotePeerSdpConstraints||!1,renegotiatingPeer:!!config.renegotiatingPeer||!1,connectionDescription:that.connectionDescription,dontGetRemoteStream:!!config.dontGetRemoteStream,extra:connection?connection.extra:{},streamsToShare:streamsToShare,isFirefoxOffered:isFirefox})},function(error){connection.enableLogs&&console.error("sdp-error",error)},defaults.sdpConstraints),peer.nativeClose=peer.close, +peer.close=function(){if(peer){try{-1===peer.iceConnectionState.search(/closed|failed/gi)&&peer.getRemoteStreams().forEach(function(stream){stream.stop()}),peer.nativeClose()}catch(e){}peer=null,that.peer=null}},this.peer=peer}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function getSTUNObj(stunStr){var urlsParam="urls";isPluginRTC&&(urlsParam="url");var obj={};return obj[urlsParam]=stunStr,obj}function getTURNObj(turnStr,username,credential){var urlsParam="urls";isPluginRTC&&(urlsParam="url");var obj={username:username,credential:credential};return obj[urlsParam]=turnStr,obj}function getExtenralIceFormatted(){var iceServers;return window.RMCExternalIceServers.forEach(function(ice){ice.urls||(ice.urls=ice.url),-1!==ice.urls.search("stun|stuns")&&iceServers.push(getSTUNObj(ice.urls)),-1!==ice.urls.search("turn|turns")&&iceServers.push(getTURNObj(ice.urls,ice.username,ice.credential))}),iceServers}function requestUserMedia(constraints){return new Promise(function(resolve,reject){getUserMedia(constraints,resolve,reject)})}function setStreamType(constraints,stream){constraints.mandatory&&constraints.mandatory.chromeMediaSource?stream.isScreen=!0:constraints.mozMediaSource||constraints.mediaSource?stream.isScreen=!0:constraints.video?stream.isVideo=!0:constraints.audio&&(stream.isAudio=!0)}function getUserMediaHandler(options){function streaming(stream,returnBack){setStreamType(options.localMediaConstraints,stream),options.onGettingLocalMedia(stream,returnBack),stream.addEventListener("ended",function(){delete currentUserMediaRequest.streams[idInstance],currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.indexOf(options)&&(delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)],currentUserMediaRequest.queueRequests=removeNullEntries(currentUserMediaRequest.queueRequests))},!1),currentUserMediaRequest.streams[idInstance]={stream:stream},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMediaHandler(currentUserMediaRequest.queueRequests.shift())}if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var idInstance=JSON.stringify(options.localMediaConstraints);if(currentUserMediaRequest.streams[idInstance])streaming(currentUserMediaRequest.streams[idInstance].stream,!0);else{if(isPluginRTC&&window.PluginRTC){document.createElement("video");return void window.PluginRTC.getUserMedia({audio:!0,video:!0},function(stream){stream.streamid=stream.id||getRandomString(),streaming(stream)},function(error){})}navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream){stream.streamid=stream.streamid||stream.id||getRandomString(),stream.idInstance=idInstance,streaming(stream)})["catch"](function(error){options.onLocalMediaError(error,options.localMediaConstraints)})}}function TextReceiver(connection){function receive(data,userid,extra){var uuid=data.uuid;if(content[uuid]||(content[uuid]=[]),content[uuid].push(data.message),data.last){var message=content[uuid].join("");data.isobject&&(message=JSON.parse(message));var receivingTime=(new Date).getTime(),latency=receivingTime-data.sendingTime,e={data:message,userid:userid,extra:extra,latency:latency};connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e),delete content[uuid]}}var content={};return{receive:receive}}var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);"undefined"!=typeof cordova&&(isMobileDevice=!0,isChrome=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0,isChrome=!0);var isPluginRTC=!isMobileDevice&&(isSafari||isIE);isPluginRTC&&"undefined"!=typeof URL&&(URL.createObjectURL=function(){});var chromeVersion=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),50),matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10)),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&tracks.push(track)}),tracks}),"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()})})),function(){function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);if(isOpera){browserName="Opera";try{fullVersion=navigator.userAgent.split("OPR/")[1].split(" ")[0],majorVersion=fullVersion.split(".")[0]}catch(e){fullVersion="0.0.0.0",majorVersion=0}}else isIE?(verOffset=nAgt.indexOf("MSIE"),browserName="IE",fullVersion=nAgt.substring(verOffset+5)):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10).toString()),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return match&&parseInt(match[1],10)>=10?!0:!1}function detectPrivateMode(callback){var isPrivate;if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){console.log(e),isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test")}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState?!0:!1},function(isTimeout){isTimeout||(isPrivate=db.result?!1:!0)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}retry(function(){return"undefined"!=typeof isPrivate?!0:!1},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){var unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Linux",r:/(Linux|X11)/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(var id in clientStrings){var cs=clientStrings[id];if(cs.r.test(nAgt)){os=cs.s;break}}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function DetectLocalIPAddress(callback){DetectRTC.isWebRTCSupported&&(DetectRTC.isORTCSupported||getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)}))}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,match=ipRegex.exec(candidate);if(!match)return void console.warn("Could not match IP address in",candidate);var ipAddress=match[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}if(RTCPeerConnection){var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}}function checkDeviceSupport(callback){if(canEnumerate){if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];"audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput");var skip;MediaDevices.forEach(function(d){d.id===device.id&&d.kind===device.kind&&(skip=!0)}),skip||(device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.label="Please invoke getUserMedia once.","https:"!==location.protocol&&document.domain.search&&-1===document.domain.search(/localhost|127.0./g)&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,-1===audioInputDevices.indexOf(device)&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,-1===audioOutputDevices.indexOf(device)&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,-1===videoInputDevices.indexOf(device)&&videoInputDevices.push(device)),-1===MediaDevices.indexOf(device)&&MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices),callback&&callback()})}}var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:"Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45"};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode&&!isEdge,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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version";if(isMobile.any())osName=isMobile.getOsName();else{var osInfo=detectDesktopOS();osName=osInfo.osName,osVersion=osInfo.osVersion}var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;checkDeviceSupport();var DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),"https:"!==location.protocol&&(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&"https:"!==location.protocol&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked=!DetectRTC.isWebSocketsSupported,DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,callback(),websocket.close(),websocket=null},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam,DetectRTC.isWebsiteHasWebcamPermissions=isWebsiteHasWebcamPermissions,DetectRTC.isWebsiteHasMicrophonePermissions=isWebsiteHasMicrophonePermissions,DetectRTC.audioInputDevices=audioInputDevices,DetectRTC.audioOutputDevices=audioOutputDevices,DetectRTC.videoInputDevices=videoInputDevices;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}(),document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection&&(RTCPeerConnection=window.RTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack;window.onPluginRTCInitialized=function(){MediaStreamTrack=window.PluginRTC.MediaStreamTrack,RTCPeerConnection=window.PluginRTC.RTCPeerConnection,RTCIceCandidate=window.PluginRTC.RTCIceCandidate,RTCSessionDescription=window.PluginRTC.RTCSessionDescription},"undefined"!=typeof window.PluginRTC&&window.onPluginRTCInitialized();var CodecsHandler=function(){function removeVPX(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtpmap:100 VP8/90000\r\n",""),sdp=sdp.replace("a=rtpmap:101 VP9/90000\r\n",""),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 100/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF ([0-9 ]*) 101/g,"m=video $1 RTP/SAVPF $2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 100([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace(/m=video ([0-9]+) RTP\/SAVPF 101([0-9 ]*)/g,"m=video $1 RTP/SAVPF$2"),sdp=sdp.replace("a=rtcp-fb:120 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:120 ccm fir\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 nack pli\r\n",""),sdp=sdp.replace("a=rtcp-fb:101 ccm fir\r\n","")}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return sdp=sdp.replace("a=rtcp-fb:126 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:126 nack pli\r\n","a=rtcp-fb:126 pli\r\n"),sdp=sdp.replace("a=rtcp-fb:97 nack\r\n",""),sdp=sdp.replace("a=rtcp-fb:97 nack pli\r\n","a=rtcp-fb:97 pli\r\n")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;ii;++i)if(0===sdpLines[i].indexOf(prefix)&&(!substr||-1!==sdpLines[i].toLowerCase().indexOf(substr.toLowerCase())))return i;return null}function getCodecPayloadType(sdpLine){var pattern=new RegExp("a=rtpmap:(\\d+) \\w+\\/\\d+"),result=sdpLine.match(pattern);return result&&2===result.length?result[1]:null}function setVideoBitrates(sdp,params){if(isMobileDevice)return sdp;params=params||{};var vp8Payload,xgoogle_min_bitrate=params.min,xgoogle_max_bitrate=params.max,sdpLines=sdp.split("\r\n"),vp8Index=findLine(sdpLines,"a=rtpmap","VP8/90000");if(vp8Index&&(vp8Payload=getCodecPayloadType(sdpLines[vp8Index])),!vp8Payload)return sdp;var rtxPayload,rtxIndex=findLine(sdpLines,"a=rtpmap","rtx/90000");if(rtxIndex&&(rtxPayload=getCodecPayloadType(sdpLines[rtxIndex])),!rtxIndex)return sdp;var rtxFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+rtxPayload.toString());if(null!==rtxFmtpLineIndex){var appendrtxNext="\r\n";appendrtxNext+="a=fmtp:"+vp8Payload+" x-google-min-bitrate="+(xgoogle_min_bitrate||"228")+"; x-google-max-bitrate="+(xgoogle_max_bitrate||"228"),sdpLines[rtxFmtpLineIndex]=sdpLines[rtxFmtpLineIndex].concat(appendrtxNext),sdp=sdpLines.join("\r\n")}return sdp}function setOpusAttributes(sdp,params){if(isMobileDevice)return sdp;params=params||{};var opusPayload,sdpLines=sdp.split("\r\n"),opusIndex=findLine(sdpLines,"a=rtpmap","opus/48000");if(opusIndex&&(opusPayload=getCodecPayloadType(sdpLines[opusIndex])),!opusPayload)return sdp;var opusFmtpLineIndex=findLine(sdpLines,"a=fmtp:"+opusPayload.toString());if(null===opusFmtpLineIndex)return sdp;var appendOpusNext="";return appendOpusNext+="; stereo="+("undefined"!=typeof params.stereo?params.stereo:"1"),appendOpusNext+="; sprop-stereo="+("undefined"!=typeof params["sprop-stereo"]?params["sprop-stereo"]:"1"),"undefined"!=typeof params.maxaveragebitrate&&(appendOpusNext+="; maxaveragebitrate="+(params.maxaveragebitrate||1048576)),"undefined"!=typeof params.maxplaybackrate&&(appendOpusNext+="; maxplaybackrate="+(params.maxplaybackrate||1048576)),"undefined"!=typeof params.cbr&&(appendOpusNext+="; cbr="+("undefined"!=typeof params.cbr?params.cbr:"1")),"undefined"!=typeof params.useinbandfec&&(appendOpusNext+="; useinbandfec="+params.useinbandfec),"undefined"!=typeof params.usedtx&&(appendOpusNext+="; usedtx="+params.usedtx),"undefined"!=typeof params.maxptime&&(appendOpusNext+="\r\na=maxptime:"+params.maxptime),sdpLines[opusFmtpLineIndex]=sdpLines[opusFmtpLineIndex].concat(appendOpusNext),sdp=sdpLines.join("\r\n")}function preferVP9(sdp){return-1===sdp.indexOf("SAVPF 100 101")||-1===sdp.indexOf("VP9/90000")?sdp:sdp.replace("SAVPF 100 101","SAVPF 101 100")}var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);return"undefined"!=typeof cordova&&(isMobileDevice=!0),navigator&&navigator.userAgent&&-1!==navigator.userAgent.indexOf("Crosswalk")&&(isMobileDevice=!0),{removeVPX:removeVPX,disableNACK:disableNACK,prioritize:prioritize,removeNonG722:removeNonG722,setApplicationSpecificBandwidth:function(sdp,bandwidth,isScreen){return setBAS(sdp,bandwidth,isScreen)},setVideoBitrates:function(sdp,params){return setVideoBitrates(sdp,params)},setOpusAttributes:function(sdp,params){return setOpusAttributes(sdp,params)},preferVP9:preferVP9}}();window.BandwidthHandler=CodecsHandler;var loadedIceFrame,OnIceCandidateHandler=function(){function processCandidates(connection,icePair){var candidate=icePair.candidate,iceRestrictions=connection.candidates,stun=iceRestrictions.stun,turn=iceRestrictions.turn;if(isNull(iceRestrictions.reflexive)||(stun=iceRestrictions.reflexive),isNull(iceRestrictions.relay)||(turn=iceRestrictions.relay),(iceRestrictions.host||!candidate.match(/typ host/g))&&(turn||!candidate.match(/typ relay/g))&&(stun||!candidate.match(/typ srflx/g))){var protocol=connection.iceProtocols;if((protocol.udp||!candidate.match(/ udp /g))&&(protocol.tcp||!candidate.match(/ tcp /g)))return connection.enableLogs&&console.debug("Your candidate pairs:",candidate),{candidate:candidate,sdpMid:icePair.sdpMid,sdpMLineIndex:icePair.sdpMLineIndex}}}return{processCandidates:processCandidates}}();"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&loadIceFrame(function(externalIceServers){externalIceServers&&externalIceServers.length&&(window.RMCExternalIceServers=externalIceServers,window.iceServersLoadCallback&&"function"==typeof window.iceServersLoadCallback&&window.iceServersLoadCallback(externalIceServers))});var IceServersHandler=function(){function getIceServers(connection){var iceServers=[];return iceServers.push(getSTUNObj("stun:stun.l.google.com:19302")),iceServers.push(getTURNObj("turn:turn.bistri.com:80","homeo","homeo")),iceServers.push(getTURNObj("turn:turn.anyfirewall.com:443","webrtc","webrtc")),window.RMCExternalIceServers?iceServers=iceServers.concat(getExtenralIceFormatted()):"undefined"!=typeof window.getExternalIceServers&&1==window.getExternalIceServers&&(connection.iceServers=iceServers,window.iceServersLoadCallback=function(){connection.iceServers=connection.iceServers.concat(getExtenralIceFormatted())}),iceServers}return{getIceServers:getIceServers}}(),getUserMedia=null,webrtcDetectedBrowser=null,webrtcDetectedVersion=null,webrtcMinimumVersion=null,webrtcUtils=window.webrtcUtils||{}; +if(webrtcUtils.enableLogs||(webrtcUtils.enableLogs=!0),webrtcUtils.log||(webrtcUtils.log=function(){webrtcUtils.enableLogs&&("undefined"!=typeof module||"function"==typeof require&&"function"==typeof define||console.log.apply(console,arguments))}),webrtcUtils.extractVersion||(webrtcUtils.extractVersion=function(uastring,expr,pos){var match=uastring.match(expr);return match&&match.length>=pos&&parseInt(match[pos],10)}),"object"==typeof window&&(!window.HTMLMediaElement||"srcObject"in window.HTMLMediaElement.prototype||Object.defineProperty(window.HTMLMediaElement.prototype,"srcObject",{get:function(){return"mozSrcObject"in this?this.mozSrcObject:this._srcObject},set:function(stream){"mozSrcObject"in this?this.mozSrcObject=stream:(this._srcObject=stream,this.src=stream?URL.createObjectURL(stream):null)}}),HTMLMediaElement.prototype.nativePlay=HTMLMediaElement.prototype.play,HTMLMediaElement.prototype.play=function(){var myself=this,promise=myself.nativePlay();promise&&promise.then(function(){setTimeout(function(){myself.nativePlay().then(function(){})["catch"](function(){alert("Video requires manual action to start the player.")})},1e3)})["catch"](function(){setTimeout(function(){myself.nativePlay().then(function(){})["catch"](function(){alert("Video requires manual action to start the player.")})},1e3)})},getUserMedia=window.navigator&&window.navigator.getUserMedia),"undefined"!=typeof window&&window.navigator)if(navigator.mozGetUserMedia&&window.mozRTCPeerConnection){if(webrtcDetectedBrowser="firefox",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Firefox\/([0-9]+)\./,1),webrtcMinimumVersion=31,getUserMedia=function(constraints,onSuccess,onError){var constraintsToFF37=function(c){if("object"!=typeof c||c.require)return c;var require=[];return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r=c[key]="object"==typeof c[key]?c[key]:{ideal:c[key]};if((void 0!==r.min||void 0!==r.max||void 0!==r.exact)&&require.push(key),void 0!==r.exact&&("number"==typeof r.exact?r.min=r.max=r.exact:c[key]=r.exact,delete r.exact),void 0!==r.ideal){c.advanced=c.advanced||[];var oc={};"number"==typeof r.ideal?oc[key]={min:r.ideal,max:r.ideal}:oc[key]=r.ideal,c.advanced.push(oc),delete r.ideal,Object.keys(r).length||delete c[key]}}}),require.length&&(c.require=require),c};return 38>webrtcDetectedVersion&&(webrtcUtils.log("spec: "+JSON.stringify(constraints)),constraints.audio&&(constraints.audio=constraintsToFF37(constraints.audio)),constraints.video&&(constraints.video=constraintsToFF37(constraints.video)),webrtcUtils.log("ff37: "+JSON.stringify(constraints))),navigator.mozGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,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)})},41>webrtcDetectedVersion){var orgEnumerateDevices=navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);navigator.mediaDevices.enumerateDevices=function(){return orgEnumerateDevices().then(void 0,function(e){if("NotFoundError"===e.name)return[];throw e})}}}else if(navigator.webkitGetUserMedia&&window.webkitRTCPeerConnection){webrtcDetectedBrowser="chrome",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Chrom(e|ium)\/([0-9]+)\./,2),webrtcMinimumVersion=38;var constraintsToChrome=function(c){if("object"!=typeof c||c.mandatory||c.optional)return c;var cc={};return Object.keys(c).forEach(function(key){if("require"!==key&&"advanced"!==key&&"mediaSource"!==key){var r="object"==typeof c[key]?c[key]:{ideal:c[key]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);var oldname=function(prefix,name){return prefix?prefix+name.charAt(0).toUpperCase()+name.slice(1):"deviceId"===name?"sourceId":name};if(void 0!==r.ideal){cc.optional=cc.optional||[];var oc={};"number"==typeof r.ideal?(oc[oldname("min",key)]=r.ideal,cc.optional.push(oc),oc={},oc[oldname("max",key)]=r.ideal,cc.optional.push(oc)):(oc[oldname("",key)]=r.ideal,cc.optional.push(oc))}void 0!==r.exact&&"number"!=typeof r.exact?(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname("",key)]=r.exact):["min","max"].forEach(function(mix){void 0!==r[mix]&&(cc.mandatory=cc.mandatory||{},cc.mandatory[oldname(mix,key)]=r[mix])})}}),c.advanced&&(cc.optional=(cc.optional||[]).concat(c.advanced)),cc};if(getUserMedia=function(constraints,onSuccess,onError){return constraints.audio&&(constraints.audio=constraintsToChrome(constraints.audio)),constraints.video&&(constraints.video=constraintsToChrome(constraints.video)),webrtcUtils.log("chrome: "+JSON.stringify(constraints)),navigator.webkitGetUserMedia(constraints,onSuccess,onError)},navigator.getUserMedia=getUserMedia,navigator.mediaDevices||(navigator.mediaDevices={getUserMedia:requestUserMedia}),navigator.mediaDevices.getUserMedia){var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return webrtcUtils.log("spec: "+JSON.stringify(c)),c.audio=constraintsToChrome(c.audio),c.video=constraintsToChrome(c.video),webrtcUtils.log("chrome: "+JSON.stringify(c)),origGetUserMedia(c)}}else navigator.mediaDevices.getUserMedia=function(constraints){return requestUserMedia(constraints)};"undefined"==typeof navigator.mediaDevices.addEventListener&&(navigator.mediaDevices.addEventListener=function(){webrtcUtils.log("Dummy mediaDevices.addEventListener called.")}),"undefined"==typeof navigator.mediaDevices.removeEventListener&&(navigator.mediaDevices.removeEventListener=function(){webrtcUtils.log("Dummy mediaDevices.removeEventListener called.")})}else navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)?(webrtcUtils.log("This appears to be Edge"),webrtcDetectedBrowser="edge",webrtcDetectedVersion=webrtcUtils.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2),webrtcMinimumVersion=12):webrtcUtils.log("Browser does not appear to be WebRTC-capable");else webrtcDetectedBrowser="not a browser";"undefined"!=typeof module?module.exports={getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}:"function"==typeof require&&"function"==typeof define&&define([],function(){return{getUserMedia:getUserMedia,webrtcDetectedBrowser:webrtcDetectedBrowser,webrtcDetectedVersion:webrtcDetectedVersion,webrtcMinimumVersion:webrtcMinimumVersion,webrtcUtils:webrtcUtils}}),"undefined"!=typeof webrtcUtils&&(webrtcUtils.enableLogs=!1);var currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[],remove:function(idInstance){this.mutex=!1;var stream=this.streams[idInstance];if(stream){stream=stream.stream;var options=stream.currentUserMediaRequestOptions;this.queueRequests.indexOf(options)&&(delete this.queueRequests[this.queueRequests.indexOf(options)],this.queueRequests=removeNullEntries(this.queueRequests)),this.streams[idInstance].stream=null,delete this.streams[idInstance]}}},StreamsHandler=function(){function handleType(type){return type?"string"==typeof type||"undefined"==typeof type?type:type.audio&&type.video?null:type.audio?"audio":type.video?"video":void 0:void 0}function setHandlers(stream,syncAction,connection){function graduallyIncreaseVolume(){if(connection.streamEvents[stream.streamid].mediaElement){var mediaElement=connection.streamEvents[stream.streamid].mediaElement;mediaElement.volume=0,afterEach(200,5,function(){mediaElement.volume+=.2})}}stream&&stream.addEventListener&&(("undefined"==typeof syncAction||1==syncAction)&&stream.addEventListener("ended",function(){StreamsHandler.onSyncNeeded(this.streamid,"ended")},!1),stream.mute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!1,connection.streamEvents[stream.streamid].isAudioMuted=!0}),("undefined"==typeof type||"video"==type)&&stream.getVideoTracks().forEach(function(track){track.enabled=!1}),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"mute",type),connection.streamEvents[stream.streamid].muteType=type||"both",fireEvent(stream,"mute",type)},stream.unmute=function(type,isSyncAction){type=handleType(type),"undefined"!=typeof isSyncAction&&(syncAction=isSyncAction),graduallyIncreaseVolume(),("undefined"==typeof type||"audio"==type)&&stream.getAudioTracks().forEach(function(track){track.enabled=!0,connection.streamEvents[stream.streamid].isAudioMuted=!1}),("undefined"==typeof type||"video"==type)&&(stream.getVideoTracks().forEach(function(track){track.enabled=!0}),"undefined"!=typeof type&&"video"==type&&connection.streamEvents[stream.streamid].isAudioMuted&&!function looper(times){times||(times=0),times++,100>times&&connection.streamEvents[stream.streamid].isAudioMuted&&(stream.mute("audio"),setTimeout(function(){looper(times)},50))}()),("undefined"==typeof syncAction||1==syncAction)&&StreamsHandler.onSyncNeeded(stream.streamid,"unmute",type),connection.streamEvents[stream.streamid].unmuteType=type||"both",fireEvent(stream,"unmute",type)})}function afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes){startedTimes=(startedTimes||0)+1,startedTimes>=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}();!function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}(),function(){function getScreenConstraints(error,sourceId){var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:error?"screen":"desktop",maxWidth:29999,maxHeight:8640,minFrameRate:30,maxFrameRate:128,minAspectRatio:1.77,googLeakyBucket:!0},optional:[]}};return sourceId&&(screen_constraints.video.mandatory.chromeMediaSourceId=sourceId),screen_constraints}function postMessage(){return iframe?iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(postMessage,100):void loadIFrame(postMessage)}function loadIFrame(loadCallback){return iframe?void loadCallback():(iframe=document.createElement("iframe"),iframe.onload=function(){iframe.isLoaded=!0,loadCallback()},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",void(document.body||document.documentElement).appendChild(iframe))}if(-1!==document.domain.indexOf("webrtc-experiment.com")){window.getScreenId=function(callback){function onIFrameCallback(event){event.data&&(event.data.chromeMediaSourceId&&("PermissionDeniedError"===event.data.chromeMediaSourceId?callback("permission-denied"):callback(null,event.data.chromeMediaSourceId,getScreenConstraints(null,event.data.chromeMediaSourceId))),event.data.chromeExtensionStatus&&callback(event.data.chromeExtensionStatus,null,getScreenConstraints(event.data.chromeExtensionStatus)),window.removeEventListener("message",onIFrameCallback))}return navigator.mozGetUserMedia?void callback(null,"firefox",{video:{mozMediaSource:"window",mediaSource:"window",width:29999,height:8640}}):(postMessage(),void window.addEventListener("message",onIFrameCallback))};var iframe;window.getScreenConstraints=function(callback){loadIFrame(function(){getScreenId(function(error,sourceId,screen_constraints){callback(error,screen_constraints.video)})})}}}();var TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(-1!==progress.position){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML=" ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(!chunk.remoteUserId||(helper=progressHelper[chunk.uuid][chunk.remoteUserId]))&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exists.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;-1!=file.type.indexOf("image")?div.innerHTML='Download '+file.name+'
':div.innerHTML='Download '+file.name+'
'}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"===response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}}}return{handle:handle}}();window.RTCMultiConnection=RTCMultiConnection}(); \ No newline at end of file diff --git a/RTCMultiConnection/package.json b/RTCMultiConnection/package.json index 31f546cb..8484f53d 100644 --- a/RTCMultiConnection/package.json +++ b/RTCMultiConnection/package.json @@ -1,7 +1,7 @@ { "name": "rtcmulticonnection-v3", "preferGlobal": false, - "version": "3.2.81", + "version": "3.2.95", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", @@ -26,7 +26,7 @@ "firefox", "opera", "ie", - "internet-explorer", + "internet-explorer", "android", "rtcweb", "rtcmulticonnection.js", @@ -49,14 +49,16 @@ "homepage": "https://rtcmulticonnection.herokuapp.com/", "_from": "rtcmulticonnection-v3@", "devDependencies": { - "grunt": "latest", - "grunt-cli": "latest", - "load-grunt-tasks": "latest", - "grunt-contrib-clean": "latest", - "grunt-contrib-concat": "latest", - "grunt-contrib-uglify": "latest", - "grunt-jsbeautifier": "latest", - "grunt-replace": "latest", - "grunt-bump": "latest" + "grunt": "0.4.5", + "grunt-bump": "0.7.0", + "grunt-cli": "0.1.13", + "grunt-contrib-clean": "0.6.0", + "grunt-contrib-concat": "0.5.1", + "grunt-contrib-copy": "0.8.2", + "grunt-contrib-uglify": "0.11.0", + "grunt-jsbeautifier": "0.2.10", + "grunt-replace": "0.11.0", + "load-grunt-tasks": "3.4.0", + "socket.io": "^0.9.17" } } diff --git a/RTCMultiConnection/server.js b/RTCMultiConnection/server.js index 379a2b05..e04761d4 100755 --- a/RTCMultiConnection/server.js +++ b/RTCMultiConnection/server.js @@ -27,7 +27,14 @@ function serverHandler(request, response) { } if (fs.statSync(filename).isDirectory()) { - if (filename.indexOf('/demos/') !== -1) { + response.writeHead(404, { + 'Content-Type': 'text/html' + }); + + if (filename.indexOf('/demos/MultiRTC/') !== -1) { + filename = filename.replace('/demos/MultiRTC/', ''); + filename += '/demos/MultiRTC/index.html'; + } else if (filename.indexOf('/demos/') !== -1) { filename = filename.replace('/demos/', ''); filename += '/demos/index.html'; } else { diff --git a/RTCMultiConnection/v2.2.2/RTCMultiConnection.js b/RTCMultiConnection/v2.2.2/RTCMultiConnection.js index b905299f..ba291212 100644 --- a/RTCMultiConnection/v2.2.2/RTCMultiConnection.js +++ b/RTCMultiConnection/v2.2.2/RTCMultiConnection.js @@ -1,4 +1,4 @@ -// Last time updated at Tuesday, December 22nd, 2015, 4:53:01 PM +// Last time updated at Wednesday, March 16th, 2016, 11:18:45 AM // Quick-Demo for newbies: http://jsfiddle.net/c46de0L8/ // Another simple demo: http://jsfiddle.net/zar6fg60/ @@ -327,6 +327,16 @@ sourceId: connection._mediaSources.audio }); } + if (connection._mediaSources.audiooutput) { + constraints.audio.optional.push({ + sourceId: connection._mediaSources.audiooutput + }); + } + if (connection._mediaSources.audioinput) { + constraints.audio.optional.push({ + sourceId: connection._mediaSources.audioinput + }); + } // if custom video device is selected if (connection._mediaSources.video) { @@ -336,6 +346,13 @@ }] }; } + if (connection._mediaSources.videoinput) { + constraints.video = { + optional: [{ + sourceId: connection._mediaSources.videoinput + }] + }; + } // for connection.session = {}; if (!session.screen && !constraints.audio && !constraints.video) { @@ -360,10 +377,10 @@ } warn(Firefox_Screen_Capturing_Warning); - screen_constraints.video = merge(screen_constraints.video.mandatory, { + screen_constraints.video = { mozMediaSource: 'window', // mozMediaSource is redundant here mediaSource: 'window' // 'screen' || 'window' - }); + }; // Firefox is supporting audio+screen from single getUserMedia request // audio+video+screen will become audio+screen for Firefox @@ -6040,7 +6057,11 @@ if (isFirefox) { // firefox supports both audio/video recording in single webm file - if (session.video) { + if (self.stream.getAudioTracks().length && self.stream.getVideoTracks().length) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } else if (session.video) { self.videoRecorder = RecordRTC(self.stream, { type: 'video' }); @@ -6050,17 +6071,36 @@ }); } } else if (isChrome) { - // chrome supports recording in two separate files: WAV and WebM - if (session.video) { + // chrome >= 48 supports MediaRecorder API + // MediaRecorder API can record remote audio+video streams as well! + + if (isMediaRecorderCompatible() && connection.DetectRTC.browser.version >= 50 && self.stream.getAudioTracks().length && self.stream.getVideoTracks().length) { self.videoRecorder = RecordRTC(self.stream, { type: 'video' }); - } + } else if (isMediaRecorderCompatible() && connection.DetectRTC.browser.version >= 50) { + if (session.video) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } else if (session.audio) { + self.audioRecorder = RecordRTC(self.stream, { + type: 'audio' + }); + } + } else { + // chrome supports recording in two separate files: WAV and WebM + if (session.video) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } - if (session.audio) { - self.audioRecorder = RecordRTC(self.stream, { - type: 'audio' - }); + if (session.audio) { + self.audioRecorder = RecordRTC(self.stream, { + type: 'audio' + }); + } } } diff --git a/RTCMultiConnection/v2.2.2/RTCMultiConnection.min.js b/RTCMultiConnection/v2.2.2/RTCMultiConnection.min.js index 38944ad3..c6b2dd0e 100755 --- a/RTCMultiConnection/v2.2.2/RTCMultiConnection.min.js +++ b/RTCMultiConnection/v2.2.2/RTCMultiConnection.min.js @@ -1,7 +1,7 @@ -// Last time updated at Tuesday, December 22nd, 2015, 4:53:02 PM +// Last time updated at Wednesday, March 16th, 2016, 11:18:46 AM !function(){function RTCMultiSession(connection,callbackForSignalingReady){function onDataChannelMessage(e){return e.data.checkingPresence&&connection.channels[e.userid]?void connection.channels[e.userid].send({presenceDetected:!0}):e.data.presenceDetected&&connection.peers[e.userid]?void(connection.peers[e.userid].connected=!0):void("text"===e.data.type?textReceiver.receive(e.data,e.userid,e.extra):connection.autoTranslateText?(e.original=e.data,connection.Translator.TranslateText(e.data,function(translatedText){e.data=translatedText,connection.onmessage(e)})):connection.onmessage(e))}function onNewSession(session){return connection.skipOnNewSession||(session.session||(session.session={}),session.extra||(session.extra={}),connection.sessionid&&session.sessionid!=connection.sessionid)?void 0:connection.onNewSession?(session.join=function(forceSession){if(!forceSession)return connection.join(session);for(var f in forceSession)session.session[f]=forceSession[f];var isDontCaptureUserMedia=connection.dontCaptureUserMedia;connection.dontCaptureUserMedia=!1,connection.captureUserMedia(function(){connection.dontCaptureUserMedia=!0,connection.join(session),connection.dontCaptureUserMedia=isDontCaptureUserMedia},forceSession)},session.extra||(session.extra={}),connection.onNewSession(session)):void connection.join(session)}function updateSocketForLocalStreams(socket){for(var i=0;i=60?socket.send({failedToReceiveRemoteVideo:!0,streamid:args.stream.streamid}):void setTimeout(function(){log("Waiting for incoming remote stream to be started flowing: "+args.numberOfTimes+" seconds."),waitUntilRemoteStreamStartsFlowing(args)},900):afterRemoteStreamStartedFlowing(args))}function initFakeChannel(){if(connection.fakeDataChannels&&!connection.channels[_config.userid]&&!connection.session.data){var fakeChannel={send:function(data){socket.send({fakeData:data})},readyState:"open"};connection.channels[_config.userid]={channel:fakeChannel,send:function(data){this.channel.send(data)}},peerConfig.onopen(fakeChannel)}}function afterRemoteStreamStartedFlowing(args){var mediaElement=args.mediaElement,session=args.session,stream=args.stream;stream.onended=function(){streamedObject.mediaElement&&!streamedObject.mediaElement.parentNode&&document.getElementById(stream.streamid)&&(streamedObject.mediaElement=document.getElementById(stream.streamid)),onStreamEndedHandler(streamedObject,connection)};var streamedObject={mediaElement:mediaElement,stream:stream,streamid:stream.streamid,session:session||connection.session,blobURL:isPluginRTC?"":mediaElement.mozSrcObject?URL.createObjectURL(stream):mediaElement.src,type:"remote",extra:_config.extra,userid:_config.userid,isVideo:isPluginRTC?!!session.video:!!stream.isVideo,isAudio:isPluginRTC?!!session.audio&&!session.video:!!stream.isAudio,isScreen:!!stream.isScreen,isInitiator:!!_config.isInitiator,rtcMultiConnection:connection,socket:socket};if(connection.streams[stream.streamid]=connection._getStream(streamedObject),connection.onstream(streamedObject),!isEmpty(args.preMuted)&&(args.preMuted.audio||args.preMuted.video)){var fakeObject=merge({},streamedObject);fakeObject.session=merge(fakeObject.session,args.preMuted),fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!1,connection.onmute(fakeObject)}log("on:add:stream",streamedObject),onSessionOpened(),connection.onspeaking&&initHark({stream:stream,streamedObject:streamedObject,connection:connection})}function onChannelOpened(channel){_config.channel=channel,connection.channels[_config.userid]={channel:_config.channel,send:function(data){connection.send(data,this.channel)}},connection.onopen({extra:_config.extra,userid:_config.userid,channel:channel});for(var q in connection.fileQueue)connection.send(connection.fileQueue[q],channel);isData(connection.session)&&onSessionOpened(),connection.partOfScreen&&connection.partOfScreen.sharing&&connection.peers[_config.userid].sharePartOfScreen(connection.partOfScreen)}function updateSocket(){socket.userid!=_config.userid&&(socket.userid=_config.userid,sockets[_config.socketIndex]=socket,connection.numberOfConnectedUsers++,connection.peers[_config.userid]={socket:socket,peer:peer,userid:_config.userid,extra:_config.extra,userinfo:_config.userinfo,addStream:function(session00){connection.addStream(session00,this.socket)},removeStream:function(streamid){return connection.streams[streamid]?(this.peer.connection.removeStream(connection.streams[streamid].stream),void this.renegotiate()):warn("No such stream exists. Stream-id:",streamid)},renegotiate:function(stream,session){connection.renegotiate(stream,session)},changeBandwidth:function(bandwidth){if(!bandwidth)throw"You MUST pass bandwidth object.";if(isString(bandwidth))throw"Pass object for bandwidth instead of string; e.g. {audio:10, video:20}";this.peer.bandwidth=bandwidth,this.socket.send({changeBandwidth:!0,bandwidth:bandwidth})},sendCustomMessage:function(message){this.socket.send({customMessage:!0,message:message})},onCustomMessage:function(message){log('Received "private" message from',this.userid,isString(message)?message:toStr(message))},drop:function(dontSendMessage){for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==connection.userid&&"local"==stream.type&&(this.peer.connection.removeStream(stream.stream),onStreamEndedHandler(stream,connection)),"remote"==stream.type&&stream.userid==this.userid&&onStreamEndedHandler(stream,connection));!dontSendMessage&&this.socket.send({drop:!0})},hold:function(holdMLine){return"answer"==peer.prevCreateType?void this.socket.send({unhold:!0,holdMLine:holdMLine||"both",takeAction:!0}):(this.socket.send({hold:!0,holdMLine:holdMLine||"both"}),this.peer.hold=!0,void this.fireHoldUnHoldEvents({kind:holdMLine,isHold:!0,userid:connection.userid,remoteUser:this.userid}))},unhold:function(holdMLine){return"answer"==peer.prevCreateType?void this.socket.send({unhold:!0,holdMLine:holdMLine||"both",takeAction:!0}):(this.socket.send({unhold:!0,holdMLine:holdMLine||"both"}),this.peer.hold=!1,void this.fireHoldUnHoldEvents({kind:holdMLine,isHold:!1,userid:connection.userid,remoteUser:this.userid}))},fireHoldUnHoldEvents:function(e){var isHold=e.isHold,kind=e.kind,userid=e.remoteUser||e.userid;for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==userid&&(isHold&&connection.onhold(merge({kind:kind},stream)),isHold||connection.onunhold(merge({kind:kind},stream))))},redial:function(){for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],stream.userid==this.userid&&"remote"==stream.type&&onStreamEndedHandler(stream,connection));log("ReDialing..."),socket.send({recreatePeer:!0}),peer=new PeerConnection,peer.create("offer",peerConfig)},sharePartOfScreen:function(args){function partOfScreenCapturer(){return that.stopPartOfScreenSharing?(that.stopPartOfScreenSharing=!1,void(connection.onpartofscreenstopped&&connection.onpartofscreenstopped())):that.pausePartOfScreenSharing?(connection.onpartofscreenpaused&&connection.onpartofscreenpaused(),setTimeout(partOfScreenCapturer,args.interval||200)):void capturePartOfScreen({element:args.element,connection:connection,callback:function(screenshot){if(!connection.channels[that.userid])throw"No such data channel exists.";screenshot!=lastScreenshot&&(lastScreenshot=screenshot,connection.channels[that.userid].send({screenshot:screenshot,isPartOfScreen:!0})),!args.once&&setTimeout(partOfScreenCapturer,args.interval||200)}})}var that=this,lastScreenshot="";partOfScreenCapturer()},getConnectionStats:function(callback,interval){function invoker(){RTCPeerConnection.prototype.getConnectionStats=window.getConnectionStats,peer.connection&&peer.connection.getConnectionStats(callback,interval)}if(!callback)throw"callback is mandatory.";window.getConnectionStats?invoker():loadScript(connection.resources.getConnectionStats,invoker)},takeSnapshot:function(callback){takeSnapshot({userid:this.userid,connection:connection,callback:callback})}})}function onSessionOpened(){if(connection.isInitiator&&getLength(participants)&&getLength(participants)<=connection.maxParticipantsAllowed&&(connection.session.oneway||connection.session.broadcast||defaultSocket.send({sessionid:connection.sessionid,newParticipant:_config.userid||socket.channel,userData:{userid:_config.userid||socket.channel,extra:_config.extra}})),"chrome"==_config.userinfo.browser&&!_config.renegotiatedOnce)for(var rSession in connection.renegotiatedSessions)_config.renegotiatedOnce=!0,connection.renegotiatedSessions[rSession]&&connection.renegotiatedSessions[rSession].stream&&connection.peers[_config.userid].renegotiate(connection.renegotiatedSessions[rSession].stream,connection.renegotiatedSessions[rSession].session)}function socketResponse(response){if(!isRMSDeleted&&response.userid!=connection.userid){if(response.sdp){_config.userid=response.userid,_config.extra=response.extra||{},_config.renegotiate=response.renegotiate,_config.streaminfo=response.streaminfo,_config.isInitiator=response.isInitiator,_config.userinfo=response.userinfo;var sdp=JSON.parse(response.sdp);"offer"==sdp.type&&(peerConfig.preferSCTP=!!response.preferSCTP,connection.fakeDataChannels=!!response.fakeDataChannels),initFakeChannel(),sdpInvoker(sdp,response.labels)}if(response.candidate&&peer&&peer.addIceCandidate(JSON.parse(response.candidate)),response.streamid&&(rtcMultiSession.streamids||(rtcMultiSession.streamids={}),rtcMultiSession.streamids[response.streamid]||(rtcMultiSession.streamids[response.streamid]=response.streamid,connection.onstreamid(response))),response.mute||response.unmute)if(response.promptMuteUnmute){if(!connection.privileges.canMuteRemoteStream)return void connection.onstatechange({userid:response.userid,extra:response.extra,name:"mute-request-denied",reason:response.userid+' tried to mute your stream; however "privileges.canMuteRemoteStream" is "false".'});connection.streams[response.streamid]&&(response.mute&&!connection.streams[response.streamid].muted&&connection.streams[response.streamid].mute(response.session),response.unmute&&connection.streams[response.streamid].muted&&connection.streams[response.streamid].unmute(response.session))}else{var streamObject={};connection.streams[response.streamid]&&(streamObject=connection.streams[response.streamid]);var session=response.session,fakeObject=merge({},streamObject);fakeObject.session=session,fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!!fakeObject.session.screen,response.mute&&connection.onmute(fakeObject||response),response.unmute&&connection.onunmute(fakeObject||response)}if(response.isVolumeChanged&&(log("Volume of stream: "+response.streamid+" has changed to: "+response.volume),connection.streams[response.streamid])){var mediaElement=connection.streams[response.streamid].mediaElement;mediaElement&&(mediaElement.volume=response.volume)}if(response.stopped&&connection.streams[response.streamid]&&onStreamEndedHandler(connection.streams[response.streamid],connection),response.promptStreamStop){if(!connection.privileges.canStopRemoteStream)return void connection.onstatechange({userid:response.userid,extra:response.extra,name:"stop-request-denied",reason:response.userid+' tried to stop your stream; however "privileges.canStopRemoteStream" is "false".'});warn("Remote stream has been manually stopped!"),connection.streams[response.streamid]&&connection.streams[response.streamid].stop()}if(response.left){if(isFirefox){var userLeft=response.userid;for(var stream in connection.streams)stream=connection.streams[stream],stream.userid==userLeft&&(connection.stopMediaStream(stream),onStreamEndedHandler(stream,connection))}if(peer&&peer.connection&&("closed"!=peer.connection.signalingState&&-1==peer.connection.iceConnectionState.search(/disconnected|failed/gi)&&peer.connection.close(),peer.connection=null),participants[response.userid]&&delete participants[response.userid],response.closeEntireSession)return connection.onSessionClosed(response),void connection.leave();connection.remove(response.userid),onLeaveHandler({userid:response.userid,extra:response.extra||{},entireSessionClosed:!!response.closeEntireSession},connection)}if(response.playRoleOfBroadcaster&&(response.extra&&(connection.extra=merge(connection.extra,response.extra)),response.participants&&(participants=response.participants,participants[connection.userid]&&delete participants[connection.userid],sockets[0]&&sockets[0].userid==response.userid&&(delete sockets[0],sockets=swap(sockets)),socketObjects[response.userid]&&delete socketObjects[response.userid]),setTimeout(connection.playRoleOfInitiator,2e3)),response.changeBandwidth){if(!connection.peers[response.userid])throw"No such peer exists.";connection.peers[response.userid].peer.bandwidth=response.bandwidth,connection.peers[response.userid].renegotiate()}if(response.customMessage){if(!connection.peers[response.userid])throw"No such peer exists.";if(response.message.ejected){if(connection.sessionDescriptions[connection.sessionid].userid!=response.userid)throw"only initiator can eject a user.";connection.leave(),connection.onSessionClosed({userid:response.userid,extra:response.extra||_config.extra,isEjected:!0})}else connection.peers[response.userid].onCustomMessage(response.message)}if(response.drop){if(!connection.peers[response.userid])throw"No such peer exists.";connection.peers[response.userid].drop(!0),connection.peers[response.userid].renegotiate(),connection.ondrop(response.userid)}if(response.hold||response.unhold){if(!connection.peers[response.userid])throw"No such peer exists.";if(response.takeAction)return void connection.peers[response.userid][response.hold?"hold":"unhold"](response.holdMLine);connection.peers[response.userid].peer.hold=!!response.hold,connection.peers[response.userid].peer.holdMLine=response.holdMLine,socket.send({isRenegotiate:!0}),connection.peers[response.userid].fireHoldUnHoldEvents({kind:response.holdMLine,isHold:!!response.hold,userid:response.userid})}response.isRenegotiate&&connection.peers[response.userid].renegotiate(null,connection.peers[response.userid].peer.session),response.fakeData&&peerConfig.onmessage(response.fakeData),response.recreatePeer&&(peer=new PeerConnection),response.failedToReceiveRemoteVideo&&(log("Remote peer hasn't received stream: "+response.streamid+". Renegotiating..."),connection.peers[response.userid]&&connection.peers[response.userid].renegotiate()),response.redial&&connection.peers[response.userid]&&("disconnected"!=connection.peers[response.userid].peer.connection.iceConnectionState&&(_config.redialing=!1),"disconnected"!=connection.peers[response.userid].peer.connection.iceConnectionState||_config.redialing||(_config.redialing=!0,warn("Peer connection is closed.",toStr(connection.peers[response.userid].peer.connection),"ReDialing.."),connection.peers[response.userid].redial()))}}function sdpInvoker(sdp,labels){function createAnswer(){peer.recreateAnswer(sdp,session,function(_sdp,streaminfo){sendsdp({sdp:_sdp,socket:socket,streaminfo:streaminfo}),connection.detachStreams=[]})}if("answer"==sdp.type)return peer.setRemoteDescription(sdp),void updateSocket();if(!_config.renegotiate&&"offer"==sdp.type)return peerConfig.offerDescription=sdp,peerConfig.session=connection.session,peer||(peer=new PeerConnection),peer.create("answer",peerConfig),void updateSocket();var session=_config.renegotiate;if(detachMediaStream(labels,peer.connection),session.oneway||isData(session))createAnswer(),delete _config.renegotiate;else{if(_config.capturing)return;_config.capturing=!0,connection.captureUserMedia(function(stream){_config.capturing=!1,peer.addStream(stream),connection.renegotiatedSessions[JSON.stringify(_config.renegotiate)]={session:_config.renegotiate,stream:stream},delete _config.renegotiate,createAnswer()},_config.renegotiate)}}var socketConfig={channel:_config.channel,onmessage:socketResponse,onopen:function(_socket){_socket&&(socket=_socket),isofferer&&!peer&&(peerConfig.session=connection.session,peer||(peer=new PeerConnection),peer.create("offer",peerConfig)),_config.socketIndex=socket.index=sockets.length,socketObjects[socketConfig.channel]=socket,sockets[_config.socketIndex]=socket,updateSocketForLocalStreams(socket),socket.__push||(socket.__push=socket.send,socket.send=function(message){message.userid=message.userid||connection.userid,message.extra=message.extra||connection.extra||{},socket.__push(message)})}};socketConfig.callback=function(_socket){socket=_socket,socketConfig.onopen()};var socket=connection.openSignalingChannel(socketConfig);socket&&socketConfig.onopen(socket);var peer,isofferer=_config.isofferer,peerConfig={onopen:onChannelOpened,onicecandidate:function(candidate){if(!connection.candidates)throw"ICE candidates are mandatory.";if(!connection.iceProtocols)throw"At least one must be true; UDP or TCP.";var iceCandidates=connection.candidates,stun=iceCandidates.stun,turn=iceCandidates.turn;if(isNull(iceCandidates.reflexive)||(stun=iceCandidates.reflexive),isNull(iceCandidates.relay)||(turn=iceCandidates.relay),(iceCandidates.host||!candidate.candidate.match(/a=candidate.*typ host/g))&&!(!turn&&candidate.candidate.match(/a=candidate.*typ relay/g)||!stun&&candidate.candidate.match(/a=candidate.*typ srflx/g))){var protocol=connection.iceProtocols;(protocol.udp||!candidate.candidate.match(/a=candidate.* udp/g))&&(protocol.tcp||!candidate.candidate.match(/a=candidate.* tcp/g))&&(window.selfNPObject||(window.selfNPObject=candidate),socket&&socket.send({candidate:JSON.stringify({candidate:candidate.candidate,sdpMid:candidate.sdpMid,sdpMLineIndex:candidate.sdpMLineIndex})}))}},onmessage:function(data){if(data){var abToStr=ab2str(data);if(-1!=abToStr.indexOf('"userid":'))abToStr=JSON.parse(abToStr),onDataChannelMessage(abToStr);else if(data instanceof ArrayBuffer||data instanceof DataView){if(!connection.enableFileSharing)throw'It seems that receiving data is either "Blob" or "File" but file sharing is disabled.';if(!rtcMultiSession.fileBufferReader){var that=this;return void initFileBufferReader(connection,function(fbr){rtcMultiSession.fileBufferReader=fbr,that.onmessage(data)})}var fileBufferReader=rtcMultiSession.fileBufferReader;return void fileBufferReader.convertToObject(data,function(chunk){return chunk.maxChunks||chunk.readyForNextChunk?chunk.readyForNextChunk?void fileBufferReader.getNextChunk(chunk.uuid,function(nextChunk,isLastChunk,extra){rtcMultiSession.send(nextChunk)}):void fileBufferReader.addChunk(chunk,function(promptNextChunk){rtcMultiSession.send(promptNextChunk)}):void connection.onmessage({data:chunk,userid:_config.userid,extra:_config.extra})})}}},onaddstream:function(stream,session){function eventListener(){setTimeout(function(){mediaElement.muted=!1,afterRemoteStreamStartedFlowing({mediaElement:mediaElement,session:session,stream:stream,preMuted:preMuted})},3e3),mediaElement.removeEventListener("play",eventListener)}if(session=session||_config.renegotiate||connection.session,!isData(session)){session.screen&&(session.audio||session.video)&&(_config.gotAudioOrVideo?(session.audio=!1,session.video=!1):(_config.gotAudioOrVideo=!0,session.screen=!1));var preMuted={};if(_config.streaminfo){var streaminfo=_config.streaminfo.split("----"),strInfo=JSON.parse(streaminfo[streaminfo.length-1]);isIE||(stream.streamid=strInfo.streamid,stream.isScreen=!!strInfo.isScreen,stream.isVideo=!!strInfo.isVideo,stream.isAudio=!!strInfo.isAudio,preMuted=strInfo.preMuted),streaminfo.pop(),_config.streaminfo=streaminfo.join("----")}var mediaElement=createMediaElement(stream,merge({remote:!0},session));return connection.setDefaultEventsForMediaElement&&connection.setDefaultEventsForMediaElement(mediaElement,stream.streamid),isPluginRTC||stream.getVideoTracks().length?void waitUntilRemoteStreamStartsFlowing({mediaElement:mediaElement,session:session,stream:stream,preMuted:preMuted}):mediaElement.addEventListener("play",eventListener,!1)}},onremovestream:function(stream){stream&&stream.streamid?(stream=connection.streams[stream.streamid],stream&&(log("on:stream:ended via on:remove:stream",stream),onStreamEndedHandler(stream,connection))):log("on:remove:stream",stream)},onclose:function(e){e.extra=_config.extra,e.userid=_config.userid,connection.onclose(e),connection.channels[e.userid]&&delete connection.channels[e.userid]},onerror:function(e){e.extra=_config.extra,e.userid=_config.userid,connection.onerror(e)},oniceconnectionstatechange:function(event){log("oniceconnectionstatechange",toStr(event)),peer.connection&&"connected"==peer.connection.iceConnectionState&&"complete"==peer.connection.iceGatheringState&&"stable"==peer.connection.signalingState&&1==connection.numberOfConnectedUsers&&connection.onconnected({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),!connection.isInitiator&&peer.connection&&"connected"==peer.connection.iceConnectionState&&"complete"==peer.connection.iceGatheringState&&"stable"==peer.connection.signalingState&&1==connection.numberOfConnectedUsers&&connection.onstatechange({userid:_config.userid,extra:_config.extra,name:"connected-with-initiator",reason:"ICE connection state seems connected; gathering state is completed; and signaling state is stable."}),connection.peers[_config.userid]&&connection.peers[_config.userid].oniceconnectionstatechange&&connection.peers[_config.userid].oniceconnectionstatechange(event),connection.peers[_config.userid]&&"failed"==connection.peers[_config.userid].peer.connection.iceConnectionState&&connection.onfailed({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),connection.peers[_config.userid]&&"disconnected"==connection.peers[_config.userid].peer.connection.iceConnectionState&&(!peer.connection.renegotiate&&connection.ondisconnected({userid:_config.userid,extra:_config.extra,peer:connection.peers[_config.userid],targetuser:_config.userinfo}),peer.connection.renegotiate=!1),connection.autoReDialOnFailure&&connection.peers[_config.userid]&&("disconnected"!=connection.peers[_config.userid].peer.connection.iceConnectionState&&(_config.redialing=!1),"disconnected"!=connection.peers[_config.userid].peer.connection.iceConnectionState||_config.redialing||(_config.redialing=!0,warn("Peer connection is closed.",toStr(connection.peers[_config.userid].peer.connection),"ReDialing.."),connection.peers[_config.userid].socket.send({redial:!0}),connection.streams.remove({remote:!0,userid:_config.userid})))},onsignalingstatechange:function(event){log("onsignalingstatechange",toStr(event))},attachStreams:connection.dontAttachStream?[]:connection.attachStreams,iceServers:connection.iceServers,rtcConfiguration:connection.rtcConfiguration,bandwidth:connection.bandwidth,sdpConstraints:connection.sdpConstraints,optionalArgument:connection.optionalArgument,disableDtlsSrtp:connection.disableDtlsSrtp,dataChannelDict:connection.dataChannelDict,preferSCTP:connection.preferSCTP,onSessionDescription:function(sessionDescription,streaminfo){sendsdp({sdp:sessionDescription,socket:socket,streaminfo:streaminfo})},trickleIce:connection.trickleIce,processSdp:connection.processSdp,sendStreamId:function(stream){socket&&socket.send({streamid:stream.streamid,isScreen:!!stream.isScreen,isAudio:!!stream.isAudio,isVideo:!!stream.isVideo})},rtcMultiConnection:connection};connection.playRoleOfInitiator=function(){connection.dontCaptureUserMedia=!0,connection.open(),sockets=swap(sockets),connection.dontCaptureUserMedia=!1},connection.askToShareParticipants=function(){defaultSocket&&defaultSocket.send({askToShareParticipants:!0})},connection.shareParticipants=function(args){var message={joinUsers:participants,userid:connection.userid,extra:connection.extra};args&&(args.dontShareWith&&(message.dontShareWith=args.dontShareWith),args.shareWith&&(message.shareWith=args.shareWith)),defaultSocket.send(message)}}function detachMediaStream(labels,peer){if(labels)for(var i=0;i1&&isString(arguments[0])&&(e={},arguments[0]&&(e.userid=arguments[0]),arguments[1]&&(e.extra=arguments[1]),arguments[2]&&(e.channel=arguments[2])),connection.captureUserMedia(function(){_accept(e)})};var isRMSDeleted=!1;this.disconnect=function(){if(this.isOwnerLeaving=!0,!connection.keepStreamsOpened){for(var streamid in connection.localStreams)connection.localStreams[streamid].stop();connection.localStreams={},currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}}connection.isInitiator&&defaultSocket.send({isDisconnectSockets:!0}),connection.refresh(),rtcMultiSession.defaultSocket=defaultSocket=null,isRMSDeleted=!0,connection.ondisconnected({userid:connection.userid,extra:connection.extra,peer:connection.peers[connection.userid],isSocketsDisconnected:!0}),connection.close(),window.removeEventListener("beforeunload",rtcMultiSession.leaveHandler),window.removeEventListener("keyup",rtcMultiSession.leaveHandler),delete this,log("Disconnected your sockets, peers, streams and everything except RTCMultiConnection object.")}}function convertToAudioStream(mediaStream){if(!mediaStream)throw"MediaStream is mandatory.";if(mediaStream.getVideoTracks&&!mediaStream.getVideoTracks().length)return mediaStream;var context=new AudioContext,mediaStreamSource=context.createMediaStreamSource(mediaStream),destination=context.createMediaStreamDestination();return mediaStreamSource.connect(destination),webAudioMediaStreamSources.push(mediaStreamSource),destination.stream}function getRandomString(){if(window.crypto&&crypto.getRandomValues&&-1==navigator.userAgent.indexOf("Safari")){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;l>i;i++)token+=a[i].toString(36);return token}return(Math.random()*(new Date).getTime()).toString(36).replace(/\./g,"")}function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function isNull(obj){return"undefined"==typeof obj}function isString(obj){return"string"==typeof obj}function isEmpty(session){var length=0;for(var s in session)length++;return 0==length}function ab2str(buf){var result="";try{result=String.fromCharCode.apply(null,new Uint16Array(buf))}catch(e){}return result}function str2ab(str){isString(str)||(str=JSON.stringify(str));for(var buf=new ArrayBuffer(2*str.length),bufView=new Uint16Array(buf),i=0,strLen=str.length;strLen>i;i++)bufView[i]=str.charCodeAt(i);return buf}function swap(arr){for(var swapped=[],length=arr.length,i=0;length>i;i++)arr[i]&&arr[i]!==!0&&swapped.push(arr[i]);return swapped}function forEach(obj,callback){for(var item in obj)callback(obj[item],item)}function log(){console.log(arguments)}function error(){console.error(arguments)}function warn(){console.warn(arguments)}function toStr(obj){return JSON.stringify(obj,function(key,value){return value&&value.sdp?(log(value.sdp.type," ",value.sdp.sdp),""):value}," ")}function getLength(obj){var length=0;for(var o in obj)o&&length++;return length}function createMediaElement(stream,session){var mediaElement=document.createElement(stream.isAudio?"audio":"video");if(mediaElement.id=stream.streamid,isPluginRTC){var body=document.body||document.documentElement;return body.insertBefore(mediaElement,body.firstChild),setTimeout(function(){Plugin.attachMediaStream(mediaElement,stream)},1e3),Plugin.attachMediaStream(mediaElement,stream)}return mediaElement[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:(window.URL||window.webkitURL).createObjectURL(stream),mediaElement.controls=!0,mediaElement.autoplay=!!session.remote,mediaElement.muted=session.remote?!1:!0,isFirefox&&mediaElement.addEventListener("ended",function(){stream.onended()},!1),mediaElement.play(),mediaElement}function onStreamEndedHandler(streamedObject,connection){(!streamedObject.mediaElement||streamedObject.mediaElement.parentNode)&&(onStreamEndedHandlerFiredFor[streamedObject.streamid]||(onStreamEndedHandlerFiredFor[streamedObject.streamid]=streamedObject,connection.onstreamended(streamedObject)))}function onLeaveHandler(event,connection){onLeaveHandlerFiredFor[event.userid]||(onLeaveHandlerFiredFor[event.userid]=event,connection.onleave(event))}function takeSnapshot(args){function _takeSnapshot(video){var canvas=document.createElement("canvas");canvas.width=video.videoWidth||video.clientWidth,canvas.height=video.videoHeight||video.clientHeight;var context=canvas.getContext("2d");context.drawImage(video,0,0,canvas.width,canvas.height),connection.snapshots[userid]=canvas.toDataURL("image/png"),args.callback&&args.callback(connection.snapshots[userid])}var userid=args.userid,connection=args.connection;if(args.mediaElement)return _takeSnapshot(args.mediaElement);for(var stream in connection.streams)stream=connection.streams[stream],stream.userid==userid&&stream.stream&&stream.stream.getVideoTracks&&stream.stream.getVideoTracks().length&&_takeSnapshot(stream.mediaElement)}function invokeMediaCaptured(connection){connection.onMediaCaptured&&(connection.onMediaCaptured(),delete connection.onMediaCaptured)}function merge(mergein,mergeto){if(mergein||(mergein={}),!mergeto)return mergein;for(var item in mergeto)mergein[item]=mergeto[item];return mergein}function loadScript(src,onload){var script=document.createElement("script");script.src=src,script.onload=function(){log("loaded resource:",src),onload&&onload()},document.documentElement.appendChild(script)}function capturePartOfScreen(args){var connection=args.connection,element=args.element;if(!window.html2canvas)return loadScript(connection.resources.html2canvas,function(){capturePartOfScreen(args)});if(isString(element)&&(element=document.querySelector(element),element||(element=document.getElementById(element))),!element)throw"HTML DOM Element is not accessible!";html2canvas(element,{onrendered:function(canvas){args.callback(canvas.toDataURL())}})}function initFileBufferReader(connection,callback){function _private(chunk){return chunk.userid=chunk.extra.userid,chunk}if(!window.FileBufferReader)return void loadScript(connection.resources.FileBufferReader,function(){initFileBufferReader(connection,callback)});var fileBufferReader=new FileBufferReader;fileBufferReader.onProgress=function(chunk){connection.onFileProgress(_private(chunk),chunk.uuid)},fileBufferReader.onBegin=function(file){connection.onFileStart(_private(file))},fileBufferReader.onEnd=function(file){connection.onFileEnd(_private(file))},callback(fileBufferReader)}function loadScreenFrame(skip){if(DetectRTC.screen.extensionid==ReservedExtensionID&&!loadedScreenFrame){if(!skip)return loadScreenFrame(!0);loadedScreenFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){iframe.isLoaded=!0,log("Screen Capturing frame is loaded.")},iframe.src="https://www.webrtc-experiment.com/getSourceId/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe),screenFrame={postMessage:function(){return iframe.isLoaded?void iframe.contentWindow.postMessage({captureSourceId:!0},"*"):void setTimeout(screenFrame.postMessage,100)}}}}function loadIceFrame(callback,skip){if(!loadedIceFrame){if(!skip)return loadIceFrame(callback,!0);loadedIceFrame=!0;var iframe=document.createElement("iframe");iframe.onload=function(){function iFrameLoaderCallback(event){event.data&&event.data.iceServers&&(callback(event.data.iceServers),window.removeEventListener("message",iFrameLoaderCallback))}iframe.isLoaded=!0,listenEventHandler("message",iFrameLoaderCallback),iframe.contentWindow.postMessage("get-ice-servers","*")},iframe.src="https://cdn.webrtc-experiment.com/getIceServers/",iframe.style.display="none",(document.body||document.documentElement).appendChild(iframe)}}function muteOrUnmute(e){var stream=e.stream,root=e.root,session=e.session||{},enabled=e.enabled;if(session.audio||session.video||(session=isString(session)?{audio:!0,video:!0}:merge(session,{audio:!0,video:!0})),session.type){if("remote"==session.type&&"remote"!=root.type)return;if("local"==session.type&&"local"!=root.type)return}if(log(enabled?"Muting":"UnMuting","session",toStr(session)),"local"==root.type&&session.audio&&stream.getAudioTracks){var audioTracks=stream.getAudioTracks()[0];audioTracks&&(audioTracks.enabled=!enabled)}if("local"==root.type&&(session.video||session.screen)&&stream.getVideoTracks){var videoTracks=stream.getVideoTracks()[0];videoTracks&&(videoTracks.enabled=!enabled)}if(root.sockets.forEach(function(socket){"local"==root.type&&socket.send({streamid:root.streamid,mute:!!enabled,unmute:!enabled,session:session}),"remote"==root.type&&socket.send({promptMuteUnmute:!0,streamid:root.streamid,mute:!!enabled,unmute:!enabled,session:session})}),"remote"!=root.type){var fakeObject=merge({},root);fakeObject.session=session,fakeObject.isAudio=!!fakeObject.session.audio&&!fakeObject.session.video,fakeObject.isVideo=!!fakeObject.session.video,fakeObject.isScreen=!!fakeObject.session.screen,enabled&&(stream.preMuted={audio:stream.getAudioTracks().length&&!stream.getAudioTracks()[0].enabled,video:stream.getVideoTracks().length&&!stream.getVideoTracks()[0].enabled},root.rtcMultiConnection.onmute(fakeObject)),enabled||(stream.preMuted={},root.rtcMultiConnection.onunmute(fakeObject))}}function initHark(args){if(!window.hark)return void loadScript(args.connection.resources.hark,function(){initHark(args)});var connection=args.connection,streamedObject=args.streamedObject,stream=args.stream,options={},speechEvents=hark(stream,options);speechEvents.on("speaking",function(){connection.onspeaking&&connection.onspeaking(streamedObject)}),speechEvents.on("stopped_speaking",function(){connection.onsilence&&connection.onsilence(streamedObject)}),speechEvents.on("volume_change",function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamedObject))})}function getUserMedia(options){function streaming(stream,returnBack,streamid){streamid||(streamid=getRandomString()),connection.localStreams[streamid]=stream;var video=options.video;video&&(video[isFirefox?"mozSrcObject":"src"]=isFirefox?stream:(window.URL||window.webkitURL).createObjectURL(stream),video.play()),options.onsuccess(stream,returnBack,idInstance,streamid),currentUserMediaRequest.streams[idInstance]={stream:stream,streamid:streamid},currentUserMediaRequest.mutex=!1,currentUserMediaRequest.queueRequests.length&&getUserMedia(currentUserMediaRequest.queueRequests.shift())}if(isPluginRTC)return Plugin.getUserMedia?Plugin.getUserMedia(options.constraints||{audio:!0,video:!0},options.onsuccess,options.onerror):void setTimeout(function(){getUserMedia(options)},1e3);if(currentUserMediaRequest.mutex===!0)return void currentUserMediaRequest.queueRequests.push(options);currentUserMediaRequest.mutex=!0;var connection=options.connection,mediaConstraints=options.mediaConstraints||{},videoConstraints="boolean"==typeof mediaConstraints.video?mediaConstraints.video:mediaConstraints.video||mediaConstraints,audioConstraints="boolean"==typeof mediaConstraints.audio?mediaConstraints.audio:mediaConstraints.audio||defaultConstraints,n=navigator,hints=options.constraints||{audio:defaultConstraints,video:defaultConstraints};hints.video&&hints.video.mozMediaSource&&(videoConstraints={}),1==hints.video&&(hints.video=defaultConstraints),1==hints.audio&&(hints.audio=defaultConstraints),"boolean"==typeof audioConstraints&&hints.audio&&(hints.audio=audioConstraints),"boolean"==typeof videoConstraints&&hints.video&&(hints.video=videoConstraints);var audioMandatoryConstraints=audioConstraints.mandatory;isEmpty(audioMandatoryConstraints)||(hints.audio.mandatory=merge(hints.audio.mandatory,audioMandatoryConstraints));var videoMandatoryConstraints=videoConstraints.mandatory;if(videoMandatoryConstraints){var mandatory={};if(videoMandatoryConstraints.minWidth&&(mandatory.minWidth=videoMandatoryConstraints.minWidth),videoMandatoryConstraints.minHeight&&(mandatory.minHeight=videoMandatoryConstraints.minHeight),videoMandatoryConstraints.maxWidth&&(mandatory.maxWidth=videoMandatoryConstraints.maxWidth),videoMandatoryConstraints.maxHeight&&(mandatory.maxHeight=videoMandatoryConstraints.maxHeight),videoMandatoryConstraints.minAspectRatio&&(mandatory.minAspectRatio=videoMandatoryConstraints.minAspectRatio),videoMandatoryConstraints.maxFrameRate&&(mandatory.maxFrameRate=videoMandatoryConstraints.maxFrameRate),videoMandatoryConstraints.minFrameRate&&(mandatory.minFrameRate=videoMandatoryConstraints.minFrameRate),mandatory.minWidth&&mandatory.minHeight){var allowed=["1920:1080","1280:720","960:720","640:360","640:480","320:240","320:180"];(-1==allowed.indexOf(mandatory.minWidth+":"+mandatory.minHeight)||-1==allowed.indexOf(mandatory.maxWidth+":"+mandatory.maxHeight))&&error('The min/max width/height constraints you passed "seems" NOT supported.',toStr(mandatory)),(mandatory.minWidth>mandatory.maxWidth||mandatory.minHeight>mandatory.maxHeight)&&error("Minimum value must not exceed maximum value.",toStr(mandatory)),mandatory.minWidth>=1280&&mandatory.minHeight>=720&&warn("Enjoy HD video! min/"+mandatory.minWidth+":"+mandatory.minHeight+", max/"+mandatory.maxWidth+":"+mandatory.maxHeight)}hints.video.mandatory=merge(hints.video.mandatory,mandatory)}videoMandatoryConstraints&&(hints.video.mandatory=merge(hints.video.mandatory,videoMandatoryConstraints)),videoConstraints.optional&&videoConstraints.optional instanceof Array&&videoConstraints.optional.length&&(hints.video.optional=hints.video.optional?hints.video.optional.concat(videoConstraints.optional):videoConstraints.optional),audioConstraints.optional&&audioConstraints.optional instanceof Array&&audioConstraints.optional.length&&(hints.audio.optional=hints.audio.optional?hints.audio.optional.concat(audioConstraints.optional):audioConstraints.optional),hints.video.mandatory&&!isEmpty(hints.video.mandatory)&&connection._mediaSources.video&&(hints.video.optional.forEach(function(video,index){video.sourceId==connection._mediaSources.video&&delete hints.video.optional[index]}),hints.video.optional=swap(hints.video.optional),hints.video.optional.push({sourceId:connection._mediaSources.video})),hints.audio.mandatory&&!isEmpty(hints.audio.mandatory)&&connection._mediaSources.audio&&(hints.audio.optional.forEach(function(audio,index){audio.sourceId==connection._mediaSources.audio&&delete hints.audio.optional[index]}),hints.audio.optional=swap(hints.audio.optional),hints.audio.optional.push({sourceId:connection._mediaSources.audio})),hints.video&&!hints.video.mozMediaSource&&hints.video.optional&&hints.video.mandatory&&!hints.video.optional.length&&isEmpty(hints.video.mandatory)&&(hints.video=!0),isMobileDevice&&(hints={audio:!!hints.audio,video:!!hints.video}),log("invoked getUserMedia with constraints:",toStr(hints));var idInstance=JSON.stringify(hints);currentUserMediaRequest.streams[idInstance]?streaming(currentUserMediaRequest.streams[idInstance].stream,!0,currentUserMediaRequest.streams[idInstance].streamid):(n.getMedia=n.webkitGetUserMedia||n.mozGetUserMedia,n.getMedia(hints,streaming,function(error){options.onerror(error,hints)}))}function setSdpConstraints(config){var sdpConstraints,sdpConstraints_mandatory={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo};return sdpConstraints={mandatory:sdpConstraints_mandatory,optional:[{VoiceActivityDetection:!1}]},navigator.mozGetUserMedia&&firefoxVersion>34&&(sdpConstraints={OfferToReceiveAudio:!!config.OfferToReceiveAudio,OfferToReceiveVideo:!!config.OfferToReceiveVideo}),sdpConstraints}function PeerConnection(){return{create:function(type,options){merge(this,options);var self=this;return this.type=type,this.init(),this.attachMediaStreams(),isFirefox&&this.session.data?(this.session.data&&"offer"==type&&this.createDataChannel(),this.getLocalDescription(type),this.session.data&&"answer"==type&&this.createDataChannel()):self.getLocalDescription(type),this},getLocalDescription:function(createType){function createDescription(){self.connection["offer"==createType?"createOffer":"createAnswer"](function(sessionDescription){sessionDescription.sdp=self.serializeSdp(sessionDescription.sdp,createType),self.connection.setLocalDescription(sessionDescription),self.trickleIce&&self.onSessionDescription(sessionDescription,self.streaminfo),"offer"==sessionDescription.type&&log("offer sdp",sessionDescription.sdp),self.prevCreateType=createType},self.onSdpError,self.constraints)}log("(getLocalDescription) peer createType is",createType),this.session.inactive&&isNull(this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing)&&(this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing=!1);var self=this;"answer"==createType?this.setRemoteDescription(this.offerDescription,createDescription):createDescription()},serializeSdp:function(sdp,createType){if(sdp=this.processSdp(sdp),isFirefox)return sdp;if(this.session.inactive&&!this.holdMLine&&(this.hold=!0,(this.session.screen||this.session.video)&&this.session.audio?this.holdMLine="both":this.session.screen||this.session.video?this.holdMLine="video":this.session.audio&&(this.holdMLine="audio")),sdp=this.setBandwidth(sdp),"both"==this.holdMLine){if(this.hold)this.prevSDP=sdp,sdp=sdp.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive");else if(this.prevSDP&&!this.session.inactive&&(sdp=this.prevSDP,35>=chromeVersion))return sdp}else if("audio"==this.holdMLine||"video"==this.holdMLine){sdp=sdp.split("m=");var audio="",video="";sdp[1]&&0==sdp[1].indexOf("audio")&&(audio="m="+sdp[1]),sdp[2]&&0==sdp[2].indexOf("audio")&&(audio="m="+sdp[2]),sdp[1]&&0==sdp[1].indexOf("video")&&(video="m="+sdp[1]),sdp[2]&&0==sdp[2].indexOf("video")&&(video="m="+sdp[2]),"audio"==this.holdMLine&&(this.hold?(this.prevSDP=sdp[0]+audio+video,sdp=sdp[0]+audio.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive")+video):this.prevSDP&&(sdp=this.prevSDP)),"video"==this.holdMLine&&(this.hold?(this.prevSDP=sdp[0]+audio+video,sdp=sdp[0]+audio+video.replace(/a=sendonly|a=recvonly|a=sendrecv/g,"a=inactive")):this.prevSDP&&(sdp=this.prevSDP))}return!this.hold&&this.session.inactive&&(sdp="offer"==createType?sdp.replace(/a=setup:passive|a=setup:active|a=setup:holdconn/g,"a=setup:actpass"):sdp.replace(/a=setup:actpass|a=setup:passive|a=setup:holdconn/g,"a=setup:active"),sdp=sdp.replace(/a=inactive/g,"a=sendrecv")),sdp},init:function(){function returnSDP(){return self.returnedSDP?void(self.returnedSDP=!1):(self.returnedSDP=!0,void self.onSessionDescription(self.connection.localDescription,self.streaminfo))}this.setConstraints(),this.connection=new RTCPeerConnection(this.iceServers,this.optionalArgument),this.session.data&&(log("invoked: createDataChannel"),this.createDataChannel()),this.connection.onicecandidate=function(event){return event.candidate?void(self.trickleIce&&self.onicecandidate(event.candidate)):void(self.trickleIce||returnSDP())},this.connection.onaddstream=function(e){log("onaddstream",isPluginRTC?e.stream:toStr(e.stream)),self.onaddstream(e.stream,self.session)},this.connection.onremovestream=function(e){self.onremovestream(e.stream)},this.connection.onsignalingstatechange=function(){self.connection&&self.oniceconnectionstatechange({iceConnectionState:self.connection.iceConnectionState,iceGatheringState:self.connection.iceGatheringState,signalingState:self.connection.signalingState})},this.connection.oniceconnectionstatechange=function(){self.connection&&(self.oniceconnectionstatechange({iceConnectionState:self.connection.iceConnectionState,iceGatheringState:self.connection.iceGatheringState,signalingState:self.connection.signalingState}),self.trickleIce||"complete"==self.connection.iceGatheringState&&(log("iceGatheringState",self.connection.iceGatheringState),returnSDP()))};var self=this},setBandwidth:function(sdp){if(isMobileDevice||isFirefox||!this.bandwidth)return sdp;var bandwidth=this.bandwidth;return this.session.screen&&(bandwidth.screen?bandwidth.screen<300&&warn("It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail."):warn("It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.")),bandwidth.screen&&this.session.screen&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,""),sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+bandwidth.screen+"\r\n")),(bandwidth.audio||bandwidth.video||bandwidth.data)&&(sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,"")),bandwidth.audio&&(sdp=sdp.replace(/a=mid:audio\r\n/g,"a=mid:audio\r\nb=AS:"+bandwidth.audio+"\r\n")),bandwidth.video&&(sdp=sdp.replace(/a=mid:video\r\n/g,"a=mid:video\r\nb=AS:"+(this.session.screen?bandwidth.screen:bandwidth.video)+"\r\n")),bandwidth.data&&!this.preferSCTP&&(sdp=sdp.replace(/a=mid:data\r\n/g,"a=mid:data\r\nb=AS:"+bandwidth.data+"\r\n")),sdp},setConstraints:function(){var sdpConstraints=setSdpConstraints({OfferToReceiveAudio:!!this.session.audio,OfferToReceiveVideo:!!this.session.video||!!this.session.screen});if(this.sdpConstraints.mandatory&&(sdpConstraints=setSdpConstraints(this.sdpConstraints.mandatory)),this.constraints=sdpConstraints,this.constraints&&log("sdp-constraints",toStr(this.constraints)),this.optionalArgument={optional:this.optionalArgument.optional||[],mandatory:this.optionalArgument.mandatory||{}},this.preferSCTP||this.optionalArgument.optional.push({RtpDataChannels:!0}),log("optional-argument",toStr(this.optionalArgument)),isNull(this.iceServers))this.iceServers=null;else{var iceCandidates=this.rtcMultiConnection.candidates,stun=iceCandidates.stun,turn=iceCandidates.turn,host=iceCandidates.host;isNull(iceCandidates.reflexive)||(stun=iceCandidates.reflexive),isNull(iceCandidates.relay)||(turn=iceCandidates.relay),host||stun||!turn?host||stun||turn||(this.rtcConfiguration.iceTransports="none"):this.rtcConfiguration.iceTransports="relay",this.iceServers={iceServers:this.iceServers,iceTransports:this.rtcConfiguration.iceTransports}}log("rtc-configuration",toStr(this.iceServers))},onSdpError:function(e){var message=toStr(e);message&&-1!=message.indexOf("RTP/SAVPF Expects at least 4 fields")&&(message="It seems that you are trying to interop RTP-datachannels with SCTP. It is not supported!"),error("onSdpError:",message)},onSdpSuccess:function(){log("sdp success")},onMediaError:function(err){error(toStr(err))},setRemoteDescription:function(sessionDescription,onSdpSuccess){if(!sessionDescription)throw"Remote session description should NOT be NULL.";if(this.connection){log("setting remote description",sessionDescription.type,sessionDescription.sdp);var self=this;this.connection.setRemoteDescription(new RTCSessionDescription(sessionDescription),onSdpSuccess||this.onSdpSuccess,function(error){ --1==error.search(/STATE_SENTINITIATE|STATE_INPROGRESS/gi)&&self.onSdpError(error)})}},addIceCandidate:function(candidate){function onAddIceCandidate(iceCandidate){self.connection.addIceCandidate(iceCandidate,function(){log("added:",candidate.sdpMid,candidate.candidate)},function(){error("onIceFailure",arguments,candidate.candidate)})}var self=this;isPluginRTC?RTCIceCandidate(candidate,function(iceCandidate){onAddIceCandidate(iceCandidate)}):onAddIceCandidate(new RTCIceCandidate(candidate))},createDataChannel:function(channelIdentifier){if(!this.channels||!this.channels.length){var self=this;this.channels||(this.channels=[]);var dataChannelDict={};this.dataChannelDict&&(dataChannelDict=this.dataChannelDict),isChrome&&!this.preferSCTP&&(dataChannelDict.reliable=!1),log("dataChannelDict",toStr(dataChannelDict)),("answer"==this.type||isFirefox)&&(this.connection.ondatachannel=function(event){self.setChannelEvents(event.channel)}),(isChrome&&"offer"==this.type||isFirefox)&&this.setChannelEvents(this.connection.createDataChannel(channelIdentifier||"channel",dataChannelDict))}},setChannelEvents:function(channel){var self=this;channel.binaryType="arraybuffer",this.dataChannelDict.binaryType&&(channel.binaryType=this.dataChannelDict.binaryType),channel.onmessage=function(event){self.onmessage(event.data)};var numberOfTimes=0;channel.onopen=function(){channel.push=channel.send,channel.send=function(data){if("disconnected"!=self.connection.iceConnectionState&&-1==channel.readyState.search(/closing|closed/g)&&-1!=channel.readyState.search(/connecting|open/g)){if("connecting"==channel.readyState)return numberOfTimes++,setTimeout(function(){if(!(20>numberOfTimes))throw"Number of times exceeded to wait for WebRTC data connection to be opened.";channel.send(data)},1e3);try{channel.push(data)}catch(e){if(numberOfTimes++,warn("Data transmission failed. Re-transmitting..",numberOfTimes,toStr(e)),numberOfTimes>=20)throw"Number of times exceeded to resend data packets over WebRTC data channels.";setTimeout(function(){channel.send(data)},100)}}},self.onopen(channel)},channel.onerror=function(event){self.onerror(event)},channel.onclose=function(event){self.onclose(event)},this.channels.push(channel)},addStream:function(stream){stream.streamid||isIE||(stream.streamid=getRandomString()),log("attaching stream:",stream.streamid,isPluginRTC?stream:toStr(stream)),this.connection.addStream(stream),this.sendStreamId(stream),this.getStreamInfo()},attachMediaStreams:function(){for(var streams=this.attachStreams,i=0;i=32?!0:!1,connection.chunkInterval=isFirefox||chromeVersion>=32?100:500,connection.chunkSize=isFirefox||chromeVersion>=32?13e3:1e3,connection.fakeDataChannels=!1,connection.waitUntilRemoteStreamStartsFlowing=null,connection.leaveOnPageUnload=!0,connection.getExternalIceServers=isChrome,connection.UA={isFirefox:isFirefox,isChrome:isChrome,isMobileDevice:isMobileDevice,version:isChrome?chromeVersion:firefoxVersion,isNodeWebkit:isNodeWebkit,isSafari:isSafari,isIE:isIE,isOpera:isOpera},connection.fileQueue={},connection.renegotiatedSessions={},connection.channels={},connection.extra={},connection.bandwidth={screen:300},connection.caniuse={RTCPeerConnection:DetectRTC.isWebRTCSupported,getUserMedia:!!navigator.webkitGetUserMedia||!!navigator.mozGetUserMedia,AudioContext:DetectRTC.isAudioContextSupported,ScreenSharing:DetectRTC.isScreenCapturingSupported,RtpDataChannels:DetectRTC.isRtpDataChannelsSupported,SctpDataChannels:DetectRTC.isSctpDataChannelsSupported},connection.snapshots={},connection._mediaSources={},connection.devices={},connection.language="en",connection.autoTranslateText=!1,connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.localStreamids=[],connection.localStreams={},connection.preRecordedMedias={},connection.attachStreams=[],connection.detachStreams=[],connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300}],mandatory:{}},connection.dataChannelDict={},connection.dontAttachStream=!1,connection.dontCaptureUserMedia=!1,connection.preventSSLAutoAllowed=!1,connection.autoReDialOnFailure=!0,connection.isInitiator=!1,connection.DetectRTC=DetectRTC,connection.trickleIce=!0,connection.sessionDescriptions={},connection.sessionDescription=null,connection.resources={RecordRTC:"https://cdn.webrtc-experiment.com/RecordRTC.js",PreRecordedMediaStreamer:"https://cdn.webrtc-experiment.com/PreRecordedMediaStreamer.js",customGetUserMediaBar:"https://cdn.webrtc-experiment.com/navigator.customGetUserMediaBar.js",html2canvas:"https://cdn.webrtc-experiment.com/screenshot.js",hark:"https://cdn.webrtc-experiment.com/hark.js",firebase:"https://cdn.webrtc-experiment.com/firebase.js",firebaseio:"https://webrtc-experiment.firebaseIO.com/",muted:"https://cdn.webrtc-experiment.com/images/muted.png",getConnectionStats:"https://cdn.webrtc-experiment.com/getConnectionStats.js",FileBufferReader:"https://cdn.webrtc-experiment.com/FileBufferReader.js"},connection.body=document.body||document.documentElement,connection.peers={},connection.firebase="chat",connection.numberOfSessions=0,connection.numberOfConnectedUsers=0,connection.enableFileSharing=!0,connection.autoSaveToDisk=!1,connection.processSdp=function(sdp){return sdp},connection.onmessage=function(e){log("onmessage",toStr(e))},connection.onopen=function(e){log("Data connection is opened between you and",e.userid)},connection.onerror=function(e){error(onerror,toStr(e))},connection.onclose=function(e){warn("onclose",toStr(e)),connection.streams.remove({userid:e.userid})};var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");div.title=file.name,div.innerHTML=" ",connection.body.insertBefore(div,connection.body.firstChild),progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},progressHelper[file.uuid].progress.max=file.maxChunks},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){progressHelper[file.uuid]&&(progressHelper[file.uuid].div.innerHTML=''+file.name+""),(connection.onFileSent||connection.onFileReceived)&&(connection.onFileSent&&connection.onFileSent(file,file.uuid),connection.onFileReceived&&connection.onFileReceived(file.name,file))},connection.onstream=function(e){connection.body.insertBefore(e.mediaElement,connection.body.firstChild)},connection.onstreamended=function(e){if(log("onStreamEndedHandler:",e),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode){if(e.mediaElement=document.getElementById(e.streamid),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode)return warn("Event.mediElement.parentNode is null.",e)}e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.onSessionClosed=function(session){session.isEjected?warn(session.userid,"ejected you."):warn("Session has been closed.",session)},connection.onmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.pause(),e.mediaElement.setAttribute("poster",e.snapshot||connection.resources.muted)),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.play(),e.mediaElement.removeAttribute("poster")),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!1)},connection.onleave=function(e){log("onleave",toStr(e))},connection.token=getRandomString,connection.peers[connection.userid]={drop:function(){connection.drop()},renegotiate:function(){},addStream:function(){},hold:function(){},unhold:function(){},changeBandwidth:function(){},sharePartOfScreen:function(){}},connection._skip=["stop","mute","unmute","_private","_selectStreams","selectFirst","selectAll","remove"],connection.streams={mute:function(session){this._private(session,!0)},unmute:function(session){this._private(session,!1)},_private:function(session,enabled){function _muteOrUnMute(stream,session,isMute){session.local&&"local"!=stream.type||session.remote&&"remote"!=stream.type||!(session.isScreen&&!stream.isScreen||session.isAudio&&!stream.isAudio||session.isVideo&&!stream.isVideo||(isMute?!stream.mute(session):!stream.unmute(session)))}if(!session||isString(session))for(var stream in this)-1==connection._skip.indexOf(stream)&&this[stream]._private(session,enabled);else for(var stream in this)-1==connection._skip.indexOf(stream)&&_muteOrUnMute(this[stream],session,enabled)},stop:function(type){function _stopStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&_stream.stop(),config.audio&&_stream.isAudio&&_stream.stop(),config.video&&_stream.isVideo&&_stream.stop(),config.audio||config.video||config.screen||_stream.stop())}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopStream(_stream,config)}else _stopStream(_stream,type);else _stream.stop()},remove:function(type){function _stopAndRemoveStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&endStream(_stream),config.audio&&_stream.isAudio&&endStream(_stream),config.video&&_stream.isVideo&&endStream(_stream),config.audio||config.video||config.screen||endStream(_stream))}function endStream(_stream){onStreamEndedHandler(_stream,connection),delete connection.streams[_stream.streamid]}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopAndRemoveStream(_stream,config)}else _stopAndRemoveStream(_stream,type);else _stopAndRemoveStream(_stream,{local:!0,remote:!0})},selectFirst:function(args){return this._selectStreams(args,!1)},selectAll:function(args){return this._selectStreams(args,!0)},_selectStreams:function(args,all){if(!args||isString(args)||isEmpty(args))throw"Invalid arguments.";isNull(args.local)&&isNull(args.remote)&&isNull(args.userid)&&(args.local=args.remote=!0),args.isAudio||args.isVideo||args.isScreen||(args.isAudio=args.isVideo=args.isScreen=!0);var selectedStreams=[];for(var stream in this)-1==connection._skip.indexOf(stream)&&(stream=this[stream])&&(args.local&&"local"==stream.type||args.remote&&"remote"==stream.type||args.userid&&stream.userid==args.userid)&&(args.isVideo&&stream.isVideo&&selectedStreams.push(stream),args.isAudio&&stream.isAudio&&selectedStreams.push(stream),args.isScreen&&stream.isScreen&&selectedStreams.push(stream));return all?selectedStreams:selectedStreams[0]}};var iceServers=[];iceServers.push({url:"stun:stun.l.google.com:19302"}),iceServers.push({url:"stun:stun.anyfirewall.com:3478"}),iceServers.push({url:"turn:turn.bistri.com:80",credential:"homeo",username:"homeo"}),iceServers.push({url:"turn:turn.anyfirewall.com:443?transport=tcp",credential:"webrtc",username:"webrtc"}),connection.iceServers=iceServers,connection.rtcConfiguration={iceServers:null,iceTransports:"all",peerIdentity:!1},connection.media={min:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.minWidth=width,connection.mediaConstraints.video.mandatory.minHeight=height)},max:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.maxWidth=width,connection.mediaConstraints.video.mandatory.maxHeight=height)}},connection._getStream=function(event){function muteOrUnmuteLocally(session,isPause,mediaElement){if(mediaElement){var lastPauseState=mediaElement.onpause,lastPlayState=mediaElement.onplay;mediaElement.onpause=mediaElement.onplay=function(){},isPause?mediaElement.pause():mediaElement.play(),mediaElement.onpause=lastPauseState,mediaElement.onplay=lastPlayState}}var resultingObject=merge({sockets:event.socket?[event.socket]:[]},event);return resultingObject.stop=function(){var self=this;if(self.sockets.forEach(function(socket){"local"==self.type&&socket.send({streamid:self.streamid,stopped:!0}),"remote"==self.type&&socket.send({promptStreamStop:!0,streamid:self.streamid})}),"remote"!=self.type){var stream=self.stream;stream&&self.rtcMultiConnection.stopMediaStream(stream)}},resultingObject.mute=function(session){this.muted=!0,this._private(session,!0)},resultingObject.unmute=function(session){this.muted=!1,this._private(session,!1)},resultingObject._private=function(session,enabled){return session&&!isNull(session.sync)&&0==session.sync?void muteOrUnmuteLocally(session,enabled,this.mediaElement):void muteOrUnmute({root:this,session:session,enabled:enabled,stream:this.stream})},resultingObject.startRecording=function(session){var self=this;return session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),window.RecordRTC?(log("started recording session",session),self.videoRecorder=self.audioRecorder=null,isFirefox?session.video?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"})):isChrome&&(session.video&&(self.videoRecorder=RecordRTC(self.stream,{type:"video"})),session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"}))),self.audioRecorder&&self.audioRecorder.startRecording(),void(self.videoRecorder&&self.videoRecorder.startRecording())):loadScript(self.rtcMultiConnection.resources.RecordRTC,function(){self.startRecording(session)})},resultingObject.stopRecording=function(callback,session){session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),log("stopped recording session",session);var self=this;session.audio&&self.audioRecorder?self.audioRecorder.stopRecording(function(){session.video&&self.videoRecorder?self.videoRecorder.stopRecording(function(){callback({audio:self.audioRecorder.getBlob(),video:self.videoRecorder.getBlob()})}):callback({audio:self.audioRecorder.getBlob()})}):session.video&&self.videoRecorder&&self.videoRecorder.stopRecording(function(){callback({video:self.videoRecorder.getBlob()})})},resultingObject.takeSnapshot=function(callback){takeSnapshot({mediaElement:this.mediaElement,userid:this.userid,connection:connection,callback:callback})},resultingObject.streamObject=resultingObject,resultingObject},connection.set=function(properties){for(var property in properties)this[property]=properties[property];return this},connection.onMediaError=function(event){error("name",event.name),error("constraintName",toStr(event.constraintName)),error("message",event.message),error("original session",event.session)},connection.takeSnapshot=function(userid,callback){takeSnapshot({userid:userid,connection:connection,callback:callback})},connection.saveToDisk=function(blob,fileName){blob.size&&blob.type?FileSaver.SaveToDisk(URL.createObjectURL(blob),fileName||blob.name||blob.type.replace("/","-")+blob.type.split("/")[1]):FileSaver.SaveToDisk(blob,fileName)},connection.selectDevices=function(device1,device2){function select(device){device&&(connection._mediaSources[device.kind]=device.id)}device1&&select(this.devices[device1]),device2&&select(this.devices[device2])},connection.getDevices=function(callback){return DetectRTC.MediaDevices.length?(DetectRTC.MediaDevices.forEach(function(device){connection.devices[device.deviceId]=device}),void(callback&&callback(connection.devices))):setTimeout(function(){connection.getDevices(callback)},1e3)},connection.getMediaDevices=connection.enumerateDevices=function(callback){if(!callback)throw"callback is mandatory.";connection.getDevices(function(){callback(connection.DetectRTC.MediaDevices)})},connection.onCustomMessage=function(message){log("Custom message",message)},connection.ondrop=function(droppedBy){log("Media connection is dropped by "+droppedBy)},connection.drop=function(config){config=config||{},connection.attachStreams=[];for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],"local"==stream.type?(connection.detachStreams.push(stream.streamid),onStreamEndedHandler(stream,connection)):onStreamEndedHandler(stream,connection));connection.sendCustomMessage({drop:!0,dontRenegotiate:isNull(config.renegotiate)?!0:config.renegotiate})},connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"==response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}},connection.setDefaultEventsForMediaElement=function(mediaElement,streamid){mediaElement.onpause=function(){connection.streams[streamid]&&!connection.streams[streamid].muted&&connection.streams[streamid].mute()},mediaElement.onplay=function(){connection.streams[streamid]&&connection.streams[streamid].muted&&connection.streams[streamid].unmute()};var volumeChangeEventFired=!1;mediaElement.onvolumechange=function(){volumeChangeEventFired||(volumeChangeEventFired=!0,connection.streams[streamid]&&setTimeout(function(){var root=connection.streams[streamid];connection.streams[streamid].sockets.forEach(function(socket){socket.send({streamid:root.streamid,isVolumeChanged:!0,volume:mediaElement.volume})}),volumeChangeEventFired=!1},2e3))}},connection.onMediaFile=function(e){log("onMediaFile",e),connection.body.appendChild(e.mediaElement)},connection.shareMediaFile=function(file,video,streamerid){return streamerid=streamerid||connection.token(),PreRecordedMediaStreamer?PreRecordedMediaStreamer.shareMediaFile({file:file,video:video,streamerid:streamerid,connection:connection}):(loadScript(connection.resources.PreRecordedMediaStreamer,function(){connection.shareMediaFile(file,video,streamerid)}),streamerid)},connection.onpartofscreen=function(e){var image=document.createElement("img");image.src=e.screenshot,connection.body.appendChild(image)},connection.skipLogs=function(){log=error=warn=function(){}},connection.hold=function(mLine){for(var peer in connection.peers)connection.peers[peer].hold(mLine)},connection.onhold=function(track){log("onhold",track),"audio"!=track.kind&&(track.mediaElement.pause(),track.mediaElement.setAttribute("poster",track.screenshot||connection.resources.muted)),"audio"==track.kind&&(track.mediaElement.muted=!0)},connection.unhold=function(mLine){for(var peer in connection.peers)connection.peers[peer].unhold(mLine)},connection.onunhold=function(track){log("onunhold",track),"audio"!=track.kind&&(track.mediaElement.play(),track.mediaElement.removeAttribute("poster")),"audio"!=track.kind&&(track.mediaElement.muted=!1)},connection.sharePartOfScreen=function(args){function partOfScreenCapturer(){(!connection.partOfScreen||connection.partOfScreen.sharing)&&capturePartOfScreen({element:args.element,connection:connection,callback:function(screenshot){if(screenshot!=lastScreenshot){lastScreenshot=screenshot;for(var channel in connection.channels)connection.channels[channel].send({screenshot:screenshot,isPartOfScreen:!0})}!args.once&&setTimeout(partOfScreenCapturer,args.interval||200)}})}var lastScreenshot="";partOfScreenCapturer(),connection.partOfScreen=merge({sharing:!0},args)},connection.pausePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.resumePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!1;connection.partOfScreen&&(connection.partOfScreen.sharing=!0)},connection.stopPartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].stopPartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.takeScreenshot=function(element,callback){if(!element||!callback)throw"Invalid number of arguments.";if(!window.html2canvas)return loadScript(connection.resources.html2canvas,function(){connection.takeScreenshot(element)});if(isString(element)&&(element=document.querySelector(element),element||(element=document.getElementById(element))),!element)throw"HTML Element is inaccessible!";html2canvas(element,{onrendered:function(canvas){callback(canvas.toDataURL())}})},connection.onScreenCapturingExtensionAvailable=function(){log("It seems that screen capturing extension is installed and available on your system!")},!isPluginRTC&&DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable=function(){connection.onScreenCapturingExtensionAvailable()}),connection.changeBandwidth=function(bandwidth){for(var peer in connection.peers)connection.peers[peer].changeBandwidth(bandwidth)},connection.convertToAudioStream=function(mediaStream){convertToAudioStream(mediaStream)},connection.onstatechange=function(state){log("on:state:change ("+state.userid+"):",state.name+":",state.reason||"")},connection.onfailed=function(event){event.peer.numOfRetries||(event.peer.numOfRetries=0),event.peer.numOfRetries++,error("ICE connectivity check is failed. Renegotiating peer connection."),event.peer.numOfRetries<2&&event.peer.renegotiate(),event.peer.numOfRetries>=2&&(event.peer.numOfRetries=0)},connection.onconnected=function(event){log("Peer connection has been established between you and",event.userid)},connection.ondisconnected=function(event){error("Peer connection seems has been disconnected between you and",event.userid),isEmpty(connection.channels)||connection.channels[event.userid]&&(connection.channels[event.userid].send({checkingPresence:!0}),setTimeout(function(){return connection.peers[event.userid].connected?void delete connection.peers[event.userid].connected:(connection.streams.remove({remote:!0,userid:event.userid}),void connection.remove(event.userid))},3e3))},connection.onstreamid=function(event){log("got remote streamid",event.streamid,"from",event.userid)},connection.stopMediaStream=function(mediaStream){if(!mediaStream)throw"MediaStream argument is mandatory.";if(connection.keepStreamsOpened)return void(mediaStream.onended&&mediaStream.onended());connection.localStreams[mediaStream.streamid]&&delete connection.localStreams[mediaStream.streamid],isFirefox&&mediaStream.onended&&mediaStream.onended();var checkForMediaStreamTrackStop=Boolean((mediaStream.getAudioTracks||mediaStream.getVideoTracks)&&(mediaStream.getAudioTracks()[0]&&!mediaStream.getAudioTracks()[0].stop||mediaStream.getVideoTracks()[0]&&!mediaStream.getVideoTracks()[0].stop));return!mediaStream.getAudioTracks||checkForMediaStreamTrackStop?void(mediaStream.stop&&mediaStream.stop()):(mediaStream.getAudioTracks().length&&mediaStream.getAudioTracks()[0].stop&&mediaStream.getAudioTracks().forEach(function(track){track.stop()}),void(mediaStream.getVideoTracks().length&&mediaStream.getVideoTracks()[0].stop&&mediaStream.getVideoTracks().forEach(function(track){track.stop()})))},connection.changeBandwidth=function(bandwidth){if(!bandwidth||isString(bandwidth)||isEmpty(bandwidth))throw'Invalid "bandwidth" arguments.';forEach(connection.peers,function(peer){peer.peer.bandwidth=bandwidth}),connection.renegotiate()},connection.openSignalingChannel=function(config){if(!window.Firebase)return loadScript(connection.resources.firebase,function(){connection.openSignalingChannel(config)});var channel=config.channel||connection.channel;connection.firebase&&(connection.resources.firebaseio=connection.resources.firebaseio.replace("//chat.","//"+connection.firebase+"."));var firebase=new Firebase(connection.resources.firebaseio+channel);firebase.channel=channel,firebase.on("child_added",function(data){config.onmessage(data.val())}),firebase.send=function(data){for(var prop in data)(isNull(data[prop])||"function"==typeof data[prop])&&(data[prop]=!1);this.push(data)},connection.socket||(connection.socket=firebase),firebase.onDisconnect().remove(),setTimeout(function(){config.callback(firebase)},1)},connection.Plugin=Plugin}window.RMCDefaultChannel=location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""),window.RTCMultiConnection=function(channel){function initRTCMultiSession(onSignalingReady){return screenFrame&&loadScreenFrame(),rtcMultiSession?onSignalingReady():void(rtcMultiSession=new RTCMultiSession(connection,onSignalingReady))}function joinSession(session,joinAs){if(isString(session)&&(connection.skipOnNewSession=!0),!rtcMultiSession)return log("Signaling channel is not ready. Connecting..."),void initRTCMultiSession(function(){log("Signaling channel is connected. Joining the session again..."),setTimeout(function(){joinSession(session,joinAs)},1e3)});if(isString(session)){if(!connection.sessionDescriptions[session])return setTimeout(function(){log("Session-Descriptions not found. Rechecking.."),joinSession(session,joinAs)},1e3);session=connection.sessionDescriptions[session]}if(joinAs)return captureUserMedia(function(){session.oneway=!0,joinSession(session)},joinAs);if(!session||!session.userid||!session.sessionid){error("missing arguments",arguments);var error='Invalid data passed over "connection.join" method.';throw connection.onstatechange({userid:"browser",extra:{},name:"Unexpected data detected.",reason:error}),error}connection.dontOverrideSession||(connection.session=session.session);var extra=connection.extra||session.extra||{};session.oneway||isData(session)?rtcMultiSession.joinSession(session,extra):captureUserMedia(function(){rtcMultiSession.joinSession(session,extra)})}function captureUserMedia(callback,_session,dontCheckChromExtension){function onIFrameCallback(event){if(event.data&&event.data.chromeMediaSourceId){window.removeEventListener("message",onIFrameCallback);var sourceId=event.data.chromeMediaSourceId;if(DetectRTC.screen.sourceId=sourceId,DetectRTC.screen.chromeMediaSource="desktop","PermissionDeniedError"==sourceId){var mediaStreamError={message:"https:"==location.protocol?"User denied to share content of his screen.":SCREEN_COMMON_FAILURE,name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.sourceId=null,connection.onMediaError(mediaStreamError)}captureUserMedia(callback,_session)}event.data&&event.data.chromeExtensionStatus&&(warn("Screen capturing extension status is:",event.data.chromeExtensionStatus),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0))}function _captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed){if(connection.onstatechange({userid:"browser",extra:{},name:"fetching-usermedia",reason:"About to capture user-media with constraints: "+toStr(forcedConstraints)}),connection.preventSSLAutoAllowed&&!dontPreventSSLAutoAllowed&&isChrome)return navigator.customGetUserMediaBar?void navigator.customGetUserMediaBar(forcedConstraints,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,!0)},function(){connection.onMediaError({name:"PermissionDeniedError",message:"User denied permission.",constraintName:forcedConstraints,session:session})}):void loadScript(connection.resources.customGetUserMediaBar,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed)});var mediaConfig={onsuccess:function(stream,returnBack,idInstance,streamid){onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session); -},onerror:function(e,constraintUsed){if(isFirefox&&"PERMISSION_DENIED"==e&&(e={message:"",name:"PermissionDeniedError",constraintName:constraintUsed,session:session}),isFirefox&&constraintUsed.video&&constraintUsed.video.mozMediaSource)return mediaStreamError={message:Firefox_Screen_Capturing_Warning,name:e.name||"PermissionDeniedError",constraintName:constraintUsed,session:session},void connection.onMediaError(mediaStreamError);if(isString(e))return connection.onMediaError({message:"Unknown Error",name:e,constraintName:constraintUsed,session:session});if(e.name&&("PermissionDeniedError"==e.name||"DevicesNotFoundError"==e.name)){var mediaStreamError="Either: ";mediaStreamError+="\n Media resolutions are not permitted.",mediaStreamError+="\n Another application is using same media device.",mediaStreamError+="\n Media device is not attached or drivers not installed.",mediaStreamError+="\n You denied access once and it is still denied.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError),isChrome&&(session.audio||session.video)&&DetectRTC.load(function(){session.audio&&!DetectRTC.hasMicrophone&&(warn("It seems that you have no microphone attached to your device/system."),session.audio=session.audio=!1,session.video||(alert("It seems that you are capturing microphone and there is no device available or access is denied. Reloading..."),location.reload())),session.video&&!DetectRTC.hasWebcam&&(warn("It seems that you have no webcam attached to your device/system."),session.video=session.video=!1,session.audio||(alert("It seems that you are capturing webcam and there is no device available or access is denied. Reloading..."),location.reload())),DetectRTC.hasMicrophone||DetectRTC.hasWebcam?connection.getUserMediaPromptedOnce||(connection.getUserMediaPromptedOnce=!0,captureUserMedia(callback,session)):(alert("It seems that either both microphone/webcam are not available or access is denied. Reloading..."),location.reload())})}if(e.name&&"ConstraintNotSatisfiedError"==e.name){var mediaStreamError="Either: ";mediaStreamError+="\n You are prompting unknown media resolutions.",mediaStreamError+="\n You are using invalid media constraints.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError)}session.screen&&(isFirefox?error(Firefox_Screen_Capturing_Warning):"https:"!==location.protocol?isNodeWebkit||"file:"!=location.protocol&&"http:"!=location.protocol||error("You cannot use HTTP or file protocol for screen capturing. You must either use HTTPs or chrome extension page or Node-Webkit page."):error('Unable to detect actual issue. Maybe "deprecated" screen capturing flag was not set using command line or maybe you clicked "No" button or maybe chrome extension returned invalid "sourceId". Please install chrome-extension: http://bit.ly/webrtc-screen-extension')),currentUserMediaRequest.mutex=!1;var idInstance=JSON.stringify(constraintUsed);currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance]},mediaConstraints:connection.mediaConstraints||{}};mediaConfig.constraints=forcedConstraints||constraints,mediaConfig.connection=connection,getUserMedia(mediaConfig)}var session=_session||connection.session;if(isEmpty(session))return void(callback&&callback());if(connection.dontCaptureUserMedia)return callback();if(isData(session)||!connection.isInitiator&&session.oneway)return connection.attachStreams=[],callback();var constraints={audio:session.audio?{mandatory:{},optional:[{chromeRenderToAssociatedSink:!0}]}:!1,video:!!session.video};if(connection._mediaSources.audio&&constraints.audio.optional.push({sourceId:connection._mediaSources.audio}),connection._mediaSources.video&&(constraints.video={optional:[{sourceId:connection._mediaSources.video}]}),!session.screen&&!constraints.audio&&!constraints.video)return callback();var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:DetectRTC.screen.chromeMediaSource,maxWidth:screen.width>1920?screen.width:1920,maxHeight:screen.height>1080?screen.height:1080},optional:[]}};if(isFirefox&&session.screen){if("https:"!==location.protocol)return error(SCREEN_COMMON_FAILURE);warn(Firefox_Screen_Capturing_Warning),screen_constraints.video=merge(screen_constraints.video.mandatory,{mozMediaSource:"window",mediaSource:"window"}),constraints.audio&&(screen_constraints.audio=!0,constraints={}),delete screen_constraints.video.chromeMediaSource}if(session.screen){if(isChrome&&DetectRTC.screen.extensionid!=ReservedExtensionID&&(useCustomChromeExtensionForScreenCapturing=!0),isChrome&&!useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&!DetectRTC.screen.sourceId)return listenEventHandler("message",onIFrameCallback),screenFrame||loadScreenFrame(),void screenFrame.postMessage();if(isChrome&&useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&"screen"==DetectRTC.screen.chromeMediaSource&&DetectRTC.screen.extensionid)return DetectRTC.screen.extensionid==ReservedExtensionID&&-1==document.domain.indexOf("webrtc-experiment.com")?captureUserMedia(callback,_session,!0):(log("checking if chrome extension is installed."),void DetectRTC.screen.getChromeExtensionStatus(function(status){"installed-enabled"==status&&(DetectRTC.screen.chromeMediaSource="desktop"),captureUserMedia(callback,_session,!0),log("chrome extension is installed?","desktop"==DetectRTC.screen.chromeMediaSource)}));if(isChrome&&useCustomChromeExtensionForScreenCapturing&&"desktop"==DetectRTC.screen.chromeMediaSource&&!DetectRTC.screen.sourceId)return void DetectRTC.screen.getSourceId(function(sourceId){if("PermissionDeniedError"==sourceId){var mediaStreamError={message:"User denied to share content of his screen.",name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.chromeMediaSource="desktop",connection.onMediaError(mediaStreamError)}return"No-Response"==sourceId?(error("Chrome extension seems not available. Make sure that manifest.json#L16 has valid content-script matches pointing to your URL."),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0)):void captureUserMedia(callback,_session,!0)});isChrome&&"desktop"==DetectRTC.screen.chromeMediaSource&&(screen_constraints.video.mandatory.chromeMediaSourceId=DetectRTC.screen.sourceId);var _isFirstSession=isFirstSession;_captureUserMedia(screen_constraints,constraints.audio||constraints.video?function(){_isFirstSession&&(isFirstSession=!0),_captureUserMedia(constraints,callback)}:callback)}else _captureUserMedia(constraints,callback,session.audio&&!session.video)}function onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session){streamid||(streamid=getRandomString()),connection.onstatechange({userid:"browser",extra:{},name:"usermedia-fetched",reason:"Captured user media using constraints: "+toStr(forcedConstraints)}),isRemoveVideoTracks&&(stream=convertToAudioStream(stream)),connection.localStreamids.push(streamid),stream.onended=function(){streamedObject.mediaElement&&!streamedObject.mediaElement.parentNode&&document.getElementById(stream.streamid)&&(streamedObject.mediaElement=document.getElementById(stream.streamid)),connection.attachStreams.forEach(function(_stream,index){_stream==stream&&(delete connection.attachStreams[index],connection.attachStreams=swap(connection.attachStreams))}),onStreamEndedHandler(streamedObject,connection),connection.streams[streamid]&&connection.removeStream(streamid);var _stream=connection.streams[streamid];_stream&&_stream.sockets.length&&_stream.sockets.forEach(function(socket){socket.send({streamid:_stream.streamid,stopped:!0})}),currentUserMediaRequest.mutex=!1,currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance],DetectRTC.screen.sourceId=null},isIE||(stream.streamid=streamid,stream.isScreen=forcedConstraints==screen_constraints,stream.isVideo=forcedConstraints==constraints&&!!constraints.video,stream.isAudio=forcedConstraints==constraints&&!!constraints.audio&&!constraints.video,stream.preMuted={audio:stream.getAudioTracks().length&&!stream.getAudioTracks()[0].enabled,video:stream.getVideoTracks().length&&!stream.getVideoTracks()[0].enabled});var mediaElement=createMediaElement(stream,session);mediaElement.muted=!0;var streamedObject={stream:stream,streamid:streamid,mediaElement:mediaElement,blobURL:mediaElement.mozSrcObject?URL.createObjectURL(stream):mediaElement.src,type:"local",userid:connection.userid,extra:connection.extra,session:session,isVideo:!!stream.isVideo,isAudio:!!stream.isAudio,isScreen:!!stream.isScreen,isInitiator:!!connection.isInitiator,rtcMultiConnection:connection};isFirstSession&&connection.attachStreams.push(stream),isFirstSession=!1,connection.streams[streamid]=connection._getStream(streamedObject),returnBack||connection.onstream(streamedObject),connection.setDefaultEventsForMediaElement&&connection.setDefaultEventsForMediaElement(mediaElement,streamid),forcedCallback&&forcedCallback(stream,streamedObject),connection.onspeaking&&initHark({stream:stream,streamedObject:streamedObject,connection:connection})}var rtcMultiSession,connection=this;connection.channel=channel||RMCDefaultChannel,connection.isAcceptNewSession=!0,connection.open=function(args){connection.isAcceptNewSession=!1,connection.isInitiator=!0;var dontTransmit=!1;return args&&(isString(args)?connection.sessionid=args:(isNull(args.transmitRoomOnce)||(connection.transmitRoomOnce=args.transmitRoomOnce),isNull(args.dontTransmit)||(dontTransmit=args.dontTransmit),isNull(args.sessionid)||(connection.sessionid=args.sessionid))),connection.socket&&connection.socket.remove&&connection.socket.remove(),connection.sessionid||(connection.sessionid=connection.channel),connection.sessionDescription={sessionid:connection.sessionid,userid:connection.userid,session:connection.session,extra:connection.extra},connection.sessionDescriptions[connection.sessionDescription.sessionid]||(connection.numberOfSessions++,connection.sessionDescriptions[connection.sessionDescription.sessionid]=connection.sessionDescription),initRTCMultiSession(function(){rtcMultiSession.captureUserMediaOnDemand=args?!!args.captureUserMediaOnDemand:!1,args&&args.onMediaCaptured&&(connection.onMediaCaptured=args.onMediaCaptured),rtcMultiSession.captureUserMediaOnDemand||captureUserMedia(function(){rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit}),invokeMediaCaptured(connection)}),rtcMultiSession.captureUserMediaOnDemand&&rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit})}),connection.sessionDescription},connection.connect=function(sessionid){return sessionid&&(connection.sessionid=sessionid),initRTCMultiSession(function(){log("Signaling channel is ready.")}),this},connection.join=joinSession,connection.send=function(data,_channel){if(connection.numberOfConnectedUsers<=0)return void setTimeout(function(){connection.send(data,_channel)},1e3);if(!data)throw"No file, data or text message to share.";if(data instanceof Array&&!isNull(data[0].size)&&!isNull(data[0].type))for(var i=0;i=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isPluginRTC=isSafari||isIE,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isNodeWebkit=!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]);window.MediaStream=window.MediaStream||window.webkitMediaStream,window.AudioContext=window.AudioContext||window.webkitAudioContext;var chromeVersion=50,matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10));var console=window.console||{log:function(){},error:function(){},warn:function(){}};if(isChrome||isFirefox||isSafari)var log=console.log.bind(console),error=console.error.bind(console),warn=console.warn.bind(console);var screenFrame,loadedScreenFrame,loadedIceFrame,onStreamEndedHandlerFiredFor={},onLeaveHandlerFiredFor={},Firefox_Screen_Capturing_Warning='Make sure that you are using Firefox Nightly and you enabled: media.getusermedia.screensharing.enabled flag from about:config page. You also need to add your domain in "media.getusermedia.screensharing.allowed_domains" flag. If you are using WinXP then also enable "media.getusermedia.screensharing.allow_on_old_platforms" flag. NEVER forget to use "only" HTTPs for screen capturing!',SCREEN_COMMON_FAILURE="HTTPs i.e. SSL-based URI is mandatory to use screen capturing.",ReservedExtensionID="ajhifddimkapgcifgcodmmfdlknahffk",useCustomChromeExtensionForScreenCapturing=-1!=document.domain.indexOf("webrtc-experiment.com");attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var Plugin=window.PluginRTC||{};window.onPluginRTCInitialized=function(pluginRTCObject){Plugin=pluginRTCObject,MediaStreamTrack=Plugin.MediaStreamTrack,RTCPeerConnection=Plugin.RTCPeerConnection,RTCIceCandidate=Plugin.RTCIceCandidate,RTCSessionDescription=Plugin.RTCSessionDescription,log(isPluginRTC?"Java-Applet":"ActiveX","plugin has been loaded.")},isEmpty(Plugin)||window.onPluginRTCInitialized(Plugin),isPluginRTC&&loadScript("https://cdn.webrtc-experiment.com/Plugin.EveryWhere.js");var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop()}),this.getVideoTracks().forEach(function(track){track.stop()})});var RTCPeerConnection,defaultConstraints={mandatory:{},optional:[]},currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]},RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate;"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:console.error("WebRTC 1.0 (RTCPeerConnection) API seems NOT available in this browser.");var FileSaver={SaveToDisk:invokeSaveAsDialog},TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,_channel),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection;if(config.text instanceof ArrayBuffer||config.text instanceof DataView)return config.channel.send(config.text,config._channel);var channel=config.channel,_channel=config._channel,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}};!function(){"use strict";function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return-1!==(verOffset=nAgt.indexOf("Opera"))?(browserName="Opera",fullVersion=nAgt.substring(verOffset+6),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("MSIE"))?(browserName="IE",fullVersion=nAgt.substring(verOffset+5)):-1!==(verOffset=nAgt.indexOf("Chrome"))?(browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):-1!==(verOffset=nAgt.indexOf("Safari"))?(browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("Firefox"))?(browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName)),isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName}}function DetectLocalIPAddress(callback){getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)})}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,ipAddress=ipRegex.exec(candidate)[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}function checkDeviceSupport(callback){return!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),navigator.enumerateDevices?(MediaDevices=[],void navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];var skip;MediaDevices.forEach(function(d){d.id===device.id&&(skip=!0)}),skip||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label||(device.label="Please invoke getUserMedia once.",isHTTPs||(device.label="HTTPs is required to get label of this "+device.kind+" device.")),("audioinput"===device.kind||"audio"===device.kind)&&(hasMicrophone=!0),"audiooutput"===device.kind&&(hasSpeakers=!0),("videoinput"===device.kind||"video"===device.kind)&&(hasWebcam=!0),MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam),callback&&callback()})):void(callback&&callback())}var navigator=window.navigator;navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)}),"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){}};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS";isMobile.any()?osName=isMobile.getOsName():(-1!==navigator.appVersion.indexOf("Win")&&(osName="Windows"),-1!==navigator.appVersion.indexOf("Mac")&&(osName="MacOS"),-1!==navigator.appVersion.indexOf("X11")&&(osName="UNIX"),-1!==navigator.appVersion.indexOf("Linux")&&(osName="Linux"));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=canEnumerate,hasSpeakers=canEnumerate,hasWebcam=canEnumerate;checkDeviceSupport();var DetectRTC={};DetectRTC.browser=getBrowserInfo(),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isHTTPs="https:"===location.protocol,isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),isHTTPs||(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported&&webAudio.isCreateMediaStreamSourceSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;if(DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked="Checking",DetectRTC.isWebSocketsSupported){var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,DetectRTC.loadCallback&&DetectRTC.loadCallback()},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,DetectRTC.loadCallback&&DetectRTC.loadCallback()}}var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=47&&!isHTTPs&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.load=function(callback){this.loadCallback=callback,checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0), -DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}();var screenCallback;DetectRTC.screen={chromeMediaSource:"screen",extensionid:ReservedExtensionID,getSourceId:function(callback){function onstatus(status){return"installed-enabled"==status?(screenCallback=callback,void window.postMessage("get-sourceId","*")):(DetectRTC.screen.chromeMediaSource="screen",void callback("No-Response"))}if(!callback)throw'"callback" parameter is mandatory.';DetectRTC.screen.status?onstatus(DetectRTC.screen.status):DetectRTC.screen.getChromeExtensionStatus(onstatus)},onMessageCallback:function(data){if(isString(data)||data.sourceId){if(log("chrome message",data),"PermissionDeniedError"==data){if(DetectRTC.screen.chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError")}"rtcmulticonnection-extension-loaded"==data&&(DetectRTC.screen.chromeMediaSource="desktop",DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable(),DetectRTC.screen.onScreenCapturingExtensionAvailable=null)),data.sourceId&&(DetectRTC.screen.sourceId=data.sourceId,screenCallback&&screenCallback(DetectRTC.screen.sourceId))}},getChromeExtensionStatus:function(extensionid,callback){function _callback(status){DetectRTC.screen.status=status,callback(status)}if(isFirefox)return _callback("not-chrome");2!=arguments.length&&(callback=extensionid,extensionid=this.extensionid);var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){DetectRTC.screen.chromeMediaSource="screen",window.postMessage("are-you-there","*"),setTimeout(function(){_callback("screen"==DetectRTC.screen.chromeMediaSource?"desktop"==DetectRTC.screen.chromeMediaSource?"installed-enabled":"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){_callback("not-installed")}}},window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.addEventListener("message",function(event){event.origin==window.location.origin&&DetectRTC.screen.onMessageCallback(event.data)})}(); \ No newline at end of file +-1==error.search(/STATE_SENTINITIATE|STATE_INPROGRESS/gi)&&self.onSdpError(error)})}},addIceCandidate:function(candidate){function onAddIceCandidate(iceCandidate){self.connection.addIceCandidate(iceCandidate,function(){log("added:",candidate.sdpMid,candidate.candidate)},function(){error("onIceFailure",arguments,candidate.candidate)})}var self=this;isPluginRTC?RTCIceCandidate(candidate,function(iceCandidate){onAddIceCandidate(iceCandidate)}):onAddIceCandidate(new RTCIceCandidate(candidate))},createDataChannel:function(channelIdentifier){if(!this.channels||!this.channels.length){var self=this;this.channels||(this.channels=[]);var dataChannelDict={};this.dataChannelDict&&(dataChannelDict=this.dataChannelDict),isChrome&&!this.preferSCTP&&(dataChannelDict.reliable=!1),log("dataChannelDict",toStr(dataChannelDict)),("answer"==this.type||isFirefox)&&(this.connection.ondatachannel=function(event){self.setChannelEvents(event.channel)}),(isChrome&&"offer"==this.type||isFirefox)&&this.setChannelEvents(this.connection.createDataChannel(channelIdentifier||"channel",dataChannelDict))}},setChannelEvents:function(channel){var self=this;channel.binaryType="arraybuffer",this.dataChannelDict.binaryType&&(channel.binaryType=this.dataChannelDict.binaryType),channel.onmessage=function(event){self.onmessage(event.data)};var numberOfTimes=0;channel.onopen=function(){channel.push=channel.send,channel.send=function(data){if("disconnected"!=self.connection.iceConnectionState&&-1==channel.readyState.search(/closing|closed/g)&&-1!=channel.readyState.search(/connecting|open/g)){if("connecting"==channel.readyState)return numberOfTimes++,setTimeout(function(){if(!(20>numberOfTimes))throw"Number of times exceeded to wait for WebRTC data connection to be opened.";channel.send(data)},1e3);try{channel.push(data)}catch(e){if(numberOfTimes++,warn("Data transmission failed. Re-transmitting..",numberOfTimes,toStr(e)),numberOfTimes>=20)throw"Number of times exceeded to resend data packets over WebRTC data channels.";setTimeout(function(){channel.send(data)},100)}}},self.onopen(channel)},channel.onerror=function(event){self.onerror(event)},channel.onclose=function(event){self.onclose(event)},this.channels.push(channel)},addStream:function(stream){stream.streamid||isIE||(stream.streamid=getRandomString()),log("attaching stream:",stream.streamid,isPluginRTC?stream:toStr(stream)),this.connection.addStream(stream),this.sendStreamId(stream),this.getStreamInfo()},attachMediaStreams:function(){for(var streams=this.attachStreams,i=0;i=32?!0:!1,connection.chunkInterval=isFirefox||chromeVersion>=32?100:500,connection.chunkSize=isFirefox||chromeVersion>=32?13e3:1e3,connection.fakeDataChannels=!1,connection.waitUntilRemoteStreamStartsFlowing=null,connection.leaveOnPageUnload=!0,connection.getExternalIceServers=isChrome,connection.UA={isFirefox:isFirefox,isChrome:isChrome,isMobileDevice:isMobileDevice,version:isChrome?chromeVersion:firefoxVersion,isNodeWebkit:isNodeWebkit,isSafari:isSafari,isIE:isIE,isOpera:isOpera},connection.fileQueue={},connection.renegotiatedSessions={},connection.channels={},connection.extra={},connection.bandwidth={screen:300},connection.caniuse={RTCPeerConnection:DetectRTC.isWebRTCSupported,getUserMedia:!!navigator.webkitGetUserMedia||!!navigator.mozGetUserMedia,AudioContext:DetectRTC.isAudioContextSupported,ScreenSharing:DetectRTC.isScreenCapturingSupported,RtpDataChannels:DetectRTC.isRtpDataChannelsSupported,SctpDataChannels:DetectRTC.isSctpDataChannelsSupported},connection.snapshots={},connection._mediaSources={},connection.devices={},connection.language="en",connection.autoTranslateText=!1,connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.localStreamids=[],connection.localStreams={},connection.preRecordedMedias={},connection.attachStreams=[],connection.detachStreams=[],connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300}],mandatory:{}},connection.dataChannelDict={},connection.dontAttachStream=!1,connection.dontCaptureUserMedia=!1,connection.preventSSLAutoAllowed=!1,connection.autoReDialOnFailure=!0,connection.isInitiator=!1,connection.DetectRTC=DetectRTC,connection.trickleIce=!0,connection.sessionDescriptions={},connection.sessionDescription=null,connection.resources={RecordRTC:"https://cdn.webrtc-experiment.com/RecordRTC.js",PreRecordedMediaStreamer:"https://cdn.webrtc-experiment.com/PreRecordedMediaStreamer.js",customGetUserMediaBar:"https://cdn.webrtc-experiment.com/navigator.customGetUserMediaBar.js",html2canvas:"https://cdn.webrtc-experiment.com/screenshot.js",hark:"https://cdn.webrtc-experiment.com/hark.js",firebase:"https://cdn.webrtc-experiment.com/firebase.js",firebaseio:"https://webrtc-experiment.firebaseIO.com/",muted:"https://cdn.webrtc-experiment.com/images/muted.png",getConnectionStats:"https://cdn.webrtc-experiment.com/getConnectionStats.js",FileBufferReader:"https://cdn.webrtc-experiment.com/FileBufferReader.js"},connection.body=document.body||document.documentElement,connection.peers={},connection.firebase="chat",connection.numberOfSessions=0,connection.numberOfConnectedUsers=0,connection.enableFileSharing=!0,connection.autoSaveToDisk=!1,connection.processSdp=function(sdp){return sdp},connection.onmessage=function(e){log("onmessage",toStr(e))},connection.onopen=function(e){log("Data connection is opened between you and",e.userid)},connection.onerror=function(e){error(onerror,toStr(e))},connection.onclose=function(e){warn("onclose",toStr(e)),connection.streams.remove({userid:e.userid})};var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");div.title=file.name,div.innerHTML=" ",connection.body.insertBefore(div,connection.body.firstChild),progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},progressHelper[file.uuid].progress.max=file.maxChunks},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label))},connection.onFileEnd=function(file){progressHelper[file.uuid]&&(progressHelper[file.uuid].div.innerHTML=''+file.name+""),(connection.onFileSent||connection.onFileReceived)&&(connection.onFileSent&&connection.onFileSent(file,file.uuid),connection.onFileReceived&&connection.onFileReceived(file.name,file))},connection.onstream=function(e){connection.body.insertBefore(e.mediaElement,connection.body.firstChild)},connection.onstreamended=function(e){if(log("onStreamEndedHandler:",e),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode){if(e.mediaElement=document.getElementById(e.streamid),!e.mediaElement)return warn("Event.mediaElement is undefined",e);if(!e.mediaElement.parentNode)return warn("Event.mediElement.parentNode is null.",e)}e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.onSessionClosed=function(session){session.isEjected?warn(session.userid,"ejected you."):warn("Session has been closed.",session)},connection.onmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.pause(),e.mediaElement.setAttribute("poster",e.snapshot||connection.resources.muted)),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e.isVideo&&e.mediaElement&&(e.mediaElement.play(),e.mediaElement.removeAttribute("poster")),e.isAudio&&e.mediaElement&&(e.mediaElement.muted=!1)},connection.onleave=function(e){log("onleave",toStr(e))},connection.token=getRandomString,connection.peers[connection.userid]={drop:function(){connection.drop()},renegotiate:function(){},addStream:function(){},hold:function(){},unhold:function(){},changeBandwidth:function(){},sharePartOfScreen:function(){}},connection._skip=["stop","mute","unmute","_private","_selectStreams","selectFirst","selectAll","remove"],connection.streams={mute:function(session){this._private(session,!0)},unmute:function(session){this._private(session,!1)},_private:function(session,enabled){function _muteOrUnMute(stream,session,isMute){session.local&&"local"!=stream.type||session.remote&&"remote"!=stream.type||!(session.isScreen&&!stream.isScreen||session.isAudio&&!stream.isAudio||session.isVideo&&!stream.isVideo||(isMute?!stream.mute(session):!stream.unmute(session)))}if(!session||isString(session))for(var stream in this)-1==connection._skip.indexOf(stream)&&this[stream]._private(session,enabled);else for(var stream in this)-1==connection._skip.indexOf(stream)&&_muteOrUnMute(this[stream],session,enabled)},stop:function(type){function _stopStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&_stream.stop(),config.audio&&_stream.isAudio&&_stream.stop(),config.video&&_stream.isVideo&&_stream.stop(),config.audio||config.video||config.screen||_stream.stop())}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopStream(_stream,config)}else _stopStream(_stream,type);else _stream.stop()},remove:function(type){function _stopAndRemoveStream(_stream,config){config.userid&&_stream.userid!=config.userid||config.local&&"local"!=_stream.type||config.remote&&"remote"!=_stream.type||(config.screen&&_stream.isScreen&&endStream(_stream),config.audio&&_stream.isAudio&&endStream(_stream),config.video&&_stream.isVideo&&endStream(_stream),config.audio||config.video||config.screen||endStream(_stream))}function endStream(_stream){onStreamEndedHandler(_stream,connection),delete connection.streams[_stream.streamid]}var _stream;for(var stream in this)if(-1==connection._skip.indexOf(stream))if(_stream=this[stream],type)if(isString(type)){var config={};config[type]=!0,_stopAndRemoveStream(_stream,config)}else _stopAndRemoveStream(_stream,type);else _stopAndRemoveStream(_stream,{local:!0,remote:!0})},selectFirst:function(args){return this._selectStreams(args,!1)},selectAll:function(args){return this._selectStreams(args,!0)},_selectStreams:function(args,all){if(!args||isString(args)||isEmpty(args))throw"Invalid arguments.";isNull(args.local)&&isNull(args.remote)&&isNull(args.userid)&&(args.local=args.remote=!0),args.isAudio||args.isVideo||args.isScreen||(args.isAudio=args.isVideo=args.isScreen=!0);var selectedStreams=[];for(var stream in this)-1==connection._skip.indexOf(stream)&&(stream=this[stream])&&(args.local&&"local"==stream.type||args.remote&&"remote"==stream.type||args.userid&&stream.userid==args.userid)&&(args.isVideo&&stream.isVideo&&selectedStreams.push(stream),args.isAudio&&stream.isAudio&&selectedStreams.push(stream),args.isScreen&&stream.isScreen&&selectedStreams.push(stream));return all?selectedStreams:selectedStreams[0]}};var iceServers=[];iceServers.push({url:"stun:stun.l.google.com:19302"}),iceServers.push({url:"stun:stun.anyfirewall.com:3478"}),iceServers.push({url:"turn:turn.bistri.com:80",credential:"homeo",username:"homeo"}),iceServers.push({url:"turn:turn.anyfirewall.com:443?transport=tcp",credential:"webrtc",username:"webrtc"}),connection.iceServers=iceServers,connection.rtcConfiguration={iceServers:null,iceTransports:"all",peerIdentity:!1},connection.media={min:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.minWidth=width,connection.mediaConstraints.video.mandatory.minHeight=height)},max:function(width,height){connection.mediaConstraints.video&&(connection.mediaConstraints.video.mandatory||(connection.mediaConstraints.video.mandatory={}),connection.mediaConstraints.video.mandatory.maxWidth=width,connection.mediaConstraints.video.mandatory.maxHeight=height)}},connection._getStream=function(event){function muteOrUnmuteLocally(session,isPause,mediaElement){if(mediaElement){var lastPauseState=mediaElement.onpause,lastPlayState=mediaElement.onplay;mediaElement.onpause=mediaElement.onplay=function(){},isPause?mediaElement.pause():mediaElement.play(),mediaElement.onpause=lastPauseState,mediaElement.onplay=lastPlayState}}var resultingObject=merge({sockets:event.socket?[event.socket]:[]},event);return resultingObject.stop=function(){var self=this;if(self.sockets.forEach(function(socket){"local"==self.type&&socket.send({streamid:self.streamid,stopped:!0}),"remote"==self.type&&socket.send({promptStreamStop:!0,streamid:self.streamid})}),"remote"!=self.type){var stream=self.stream;stream&&self.rtcMultiConnection.stopMediaStream(stream)}},resultingObject.mute=function(session){this.muted=!0,this._private(session,!0)},resultingObject.unmute=function(session){this.muted=!1,this._private(session,!1)},resultingObject._private=function(session,enabled){return session&&!isNull(session.sync)&&0==session.sync?void muteOrUnmuteLocally(session,enabled,this.mediaElement):void muteOrUnmute({root:this,session:session,enabled:enabled,stream:this.stream})},resultingObject.startRecording=function(session){var self=this;return session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),window.RecordRTC?(log("started recording session",session),self.videoRecorder=self.audioRecorder=null,isFirefox?self.stream.getAudioTracks().length&&self.stream.getVideoTracks().length?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):session.video?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"})):isChrome&&(isMediaRecorderCompatible()&&connection.DetectRTC.browser.version>=50&&self.stream.getAudioTracks().length&&self.stream.getVideoTracks().length?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):isMediaRecorderCompatible()&&connection.DetectRTC.browser.version>=50?session.video?self.videoRecorder=RecordRTC(self.stream,{type:"video"}):session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"})):(session.video&&(self.videoRecorder=RecordRTC(self.stream,{type:"video"})),session.audio&&(self.audioRecorder=RecordRTC(self.stream,{type:"audio"})))),self.audioRecorder&&self.audioRecorder.startRecording(),void(self.videoRecorder&&self.videoRecorder.startRecording())):loadScript(self.rtcMultiConnection.resources.RecordRTC,function(){self.startRecording(session)})},resultingObject.stopRecording=function(callback,session){session||(session={audio:!0,video:!0}),isString(session)&&(session={audio:"audio"==session,video:"video"==session}),log("stopped recording session",session);var self=this;session.audio&&self.audioRecorder?self.audioRecorder.stopRecording(function(){session.video&&self.videoRecorder?self.videoRecorder.stopRecording(function(){callback({audio:self.audioRecorder.getBlob(),video:self.videoRecorder.getBlob()})}):callback({audio:self.audioRecorder.getBlob()})}):session.video&&self.videoRecorder&&self.videoRecorder.stopRecording(function(){callback({video:self.videoRecorder.getBlob()})})},resultingObject.takeSnapshot=function(callback){takeSnapshot({mediaElement:this.mediaElement,userid:this.userid,connection:connection,callback:callback})},resultingObject.streamObject=resultingObject,resultingObject},connection.set=function(properties){for(var property in properties)this[property]=properties[property];return this},connection.onMediaError=function(event){error("name",event.name),error("constraintName",toStr(event.constraintName)),error("message",event.message),error("original session",event.session)},connection.takeSnapshot=function(userid,callback){takeSnapshot({userid:userid,connection:connection,callback:callback})},connection.saveToDisk=function(blob,fileName){blob.size&&blob.type?FileSaver.SaveToDisk(URL.createObjectURL(blob),fileName||blob.name||blob.type.replace("/","-")+blob.type.split("/")[1]):FileSaver.SaveToDisk(blob,fileName)},connection.selectDevices=function(device1,device2){function select(device){device&&(connection._mediaSources[device.kind]=device.id)}device1&&select(this.devices[device1]),device2&&select(this.devices[device2])},connection.getDevices=function(callback){return DetectRTC.MediaDevices.length?(DetectRTC.MediaDevices.forEach(function(device){connection.devices[device.deviceId]=device}),void(callback&&callback(connection.devices))):setTimeout(function(){connection.getDevices(callback)},1e3)},connection.getMediaDevices=connection.enumerateDevices=function(callback){if(!callback)throw"callback is mandatory.";connection.getDevices(function(){callback(connection.DetectRTC.MediaDevices)})},connection.onCustomMessage=function(message){log("Custom message",message)},connection.ondrop=function(droppedBy){log("Media connection is dropped by "+droppedBy)},connection.drop=function(config){config=config||{},connection.attachStreams=[];for(var stream in connection.streams)-1==connection._skip.indexOf(stream)&&(stream=connection.streams[stream],"local"==stream.type?(connection.detachStreams.push(stream.streamid),onStreamEndedHandler(stream,connection)):onStreamEndedHandler(stream,connection));connection.sendCustomMessage({drop:!0,dontRenegotiate:isNull(config.renegotiate)?!0:config.renegotiate})},connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){response.data&&response.data.translations[0]&&callback&&callback(response.data.translations[0].translatedText),response.error&&"Daily Limit Exceeded"==response.error.message&&(warn('Text translation failed. Error message: "Daily Limit Exceeded."'),callback(text))};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)}},connection.setDefaultEventsForMediaElement=function(mediaElement,streamid){mediaElement.onpause=function(){connection.streams[streamid]&&!connection.streams[streamid].muted&&connection.streams[streamid].mute()},mediaElement.onplay=function(){connection.streams[streamid]&&connection.streams[streamid].muted&&connection.streams[streamid].unmute()};var volumeChangeEventFired=!1;mediaElement.onvolumechange=function(){volumeChangeEventFired||(volumeChangeEventFired=!0,connection.streams[streamid]&&setTimeout(function(){var root=connection.streams[streamid];connection.streams[streamid].sockets.forEach(function(socket){socket.send({streamid:root.streamid,isVolumeChanged:!0,volume:mediaElement.volume})}),volumeChangeEventFired=!1},2e3))}},connection.onMediaFile=function(e){log("onMediaFile",e),connection.body.appendChild(e.mediaElement)},connection.shareMediaFile=function(file,video,streamerid){return streamerid=streamerid||connection.token(),PreRecordedMediaStreamer?PreRecordedMediaStreamer.shareMediaFile({file:file,video:video,streamerid:streamerid,connection:connection}):(loadScript(connection.resources.PreRecordedMediaStreamer,function(){connection.shareMediaFile(file,video,streamerid)}),streamerid)},connection.onpartofscreen=function(e){var image=document.createElement("img");image.src=e.screenshot,connection.body.appendChild(image)},connection.skipLogs=function(){log=error=warn=function(){}},connection.hold=function(mLine){for(var peer in connection.peers)connection.peers[peer].hold(mLine)},connection.onhold=function(track){log("onhold",track),"audio"!=track.kind&&(track.mediaElement.pause(),track.mediaElement.setAttribute("poster",track.screenshot||connection.resources.muted)),"audio"==track.kind&&(track.mediaElement.muted=!0)},connection.unhold=function(mLine){for(var peer in connection.peers)connection.peers[peer].unhold(mLine)},connection.onunhold=function(track){log("onunhold",track),"audio"!=track.kind&&(track.mediaElement.play(),track.mediaElement.removeAttribute("poster")),"audio"!=track.kind&&(track.mediaElement.muted=!1)},connection.sharePartOfScreen=function(args){function partOfScreenCapturer(){(!connection.partOfScreen||connection.partOfScreen.sharing)&&capturePartOfScreen({element:args.element,connection:connection,callback:function(screenshot){if(screenshot!=lastScreenshot){lastScreenshot=screenshot;for(var channel in connection.channels)connection.channels[channel].send({screenshot:screenshot,isPartOfScreen:!0})}!args.once&&setTimeout(partOfScreenCapturer,args.interval||200)}})}var lastScreenshot="";partOfScreenCapturer(),connection.partOfScreen=merge({sharing:!0},args)},connection.pausePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.resumePartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].pausePartOfScreenSharing=!1;connection.partOfScreen&&(connection.partOfScreen.sharing=!0)},connection.stopPartOfScreenSharing=function(){for(var peer in connection.peers)connection.peers[peer].stopPartOfScreenSharing=!0;connection.partOfScreen&&(connection.partOfScreen.sharing=!1)},connection.takeScreenshot=function(element,callback){if(!element||!callback)throw"Invalid number of arguments.";if(!window.html2canvas)return loadScript(connection.resources.html2canvas,function(){connection.takeScreenshot(element)});if(isString(element)&&(element=document.querySelector(element),element||(element=document.getElementById(element))),!element)throw"HTML Element is inaccessible!";html2canvas(element,{onrendered:function(canvas){callback(canvas.toDataURL())}})},connection.onScreenCapturingExtensionAvailable=function(){log("It seems that screen capturing extension is installed and available on your system!")},!isPluginRTC&&DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable=function(){connection.onScreenCapturingExtensionAvailable()}),connection.changeBandwidth=function(bandwidth){for(var peer in connection.peers)connection.peers[peer].changeBandwidth(bandwidth)},connection.convertToAudioStream=function(mediaStream){convertToAudioStream(mediaStream)},connection.onstatechange=function(state){log("on:state:change ("+state.userid+"):",state.name+":",state.reason||"")},connection.onfailed=function(event){event.peer.numOfRetries||(event.peer.numOfRetries=0),event.peer.numOfRetries++,error("ICE connectivity check is failed. Renegotiating peer connection."),event.peer.numOfRetries<2&&event.peer.renegotiate(),event.peer.numOfRetries>=2&&(event.peer.numOfRetries=0)},connection.onconnected=function(event){log("Peer connection has been established between you and",event.userid)},connection.ondisconnected=function(event){error("Peer connection seems has been disconnected between you and",event.userid),isEmpty(connection.channels)||connection.channels[event.userid]&&(connection.channels[event.userid].send({checkingPresence:!0}),setTimeout(function(){return connection.peers[event.userid].connected?void delete connection.peers[event.userid].connected:(connection.streams.remove({remote:!0,userid:event.userid}),void connection.remove(event.userid))},3e3))},connection.onstreamid=function(event){log("got remote streamid",event.streamid,"from",event.userid)},connection.stopMediaStream=function(mediaStream){if(!mediaStream)throw"MediaStream argument is mandatory.";if(connection.keepStreamsOpened)return void(mediaStream.onended&&mediaStream.onended());connection.localStreams[mediaStream.streamid]&&delete connection.localStreams[mediaStream.streamid],isFirefox&&mediaStream.onended&&mediaStream.onended();var checkForMediaStreamTrackStop=Boolean((mediaStream.getAudioTracks||mediaStream.getVideoTracks)&&(mediaStream.getAudioTracks()[0]&&!mediaStream.getAudioTracks()[0].stop||mediaStream.getVideoTracks()[0]&&!mediaStream.getVideoTracks()[0].stop));return!mediaStream.getAudioTracks||checkForMediaStreamTrackStop?void(mediaStream.stop&&mediaStream.stop()):(mediaStream.getAudioTracks().length&&mediaStream.getAudioTracks()[0].stop&&mediaStream.getAudioTracks().forEach(function(track){track.stop()}),void(mediaStream.getVideoTracks().length&&mediaStream.getVideoTracks()[0].stop&&mediaStream.getVideoTracks().forEach(function(track){track.stop()})))},connection.changeBandwidth=function(bandwidth){if(!bandwidth||isString(bandwidth)||isEmpty(bandwidth))throw'Invalid "bandwidth" arguments.';forEach(connection.peers,function(peer){peer.peer.bandwidth=bandwidth}),connection.renegotiate()},connection.openSignalingChannel=function(config){if(!window.Firebase)return loadScript(connection.resources.firebase,function(){connection.openSignalingChannel(config)});var channel=config.channel||connection.channel;connection.firebase&&(connection.resources.firebaseio=connection.resources.firebaseio.replace("//chat.","//"+connection.firebase+"."));var firebase=new Firebase(connection.resources.firebaseio+channel);firebase.channel=channel,firebase.on("child_added",function(data){config.onmessage(data.val())}),firebase.send=function(data){for(var prop in data)(isNull(data[prop])||"function"==typeof data[prop])&&(data[prop]=!1);this.push(data)},connection.socket||(connection.socket=firebase),firebase.onDisconnect().remove(),setTimeout(function(){config.callback(firebase)},1)},connection.Plugin=Plugin}window.RMCDefaultChannel=location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""),window.RTCMultiConnection=function(channel){function initRTCMultiSession(onSignalingReady){return screenFrame&&loadScreenFrame(),rtcMultiSession?onSignalingReady():void(rtcMultiSession=new RTCMultiSession(connection,onSignalingReady))}function joinSession(session,joinAs){if(isString(session)&&(connection.skipOnNewSession=!0),!rtcMultiSession)return log("Signaling channel is not ready. Connecting..."),void initRTCMultiSession(function(){log("Signaling channel is connected. Joining the session again..."),setTimeout(function(){joinSession(session,joinAs)},1e3)});if(isString(session)){if(!connection.sessionDescriptions[session])return setTimeout(function(){log("Session-Descriptions not found. Rechecking.."),joinSession(session,joinAs)},1e3);session=connection.sessionDescriptions[session]}if(joinAs)return captureUserMedia(function(){session.oneway=!0,joinSession(session)},joinAs);if(!session||!session.userid||!session.sessionid){error("missing arguments",arguments);var error='Invalid data passed over "connection.join" method.';throw connection.onstatechange({userid:"browser",extra:{},name:"Unexpected data detected.",reason:error}),error}connection.dontOverrideSession||(connection.session=session.session);var extra=connection.extra||session.extra||{};session.oneway||isData(session)?rtcMultiSession.joinSession(session,extra):captureUserMedia(function(){rtcMultiSession.joinSession(session,extra)})}function captureUserMedia(callback,_session,dontCheckChromExtension){function onIFrameCallback(event){if(event.data&&event.data.chromeMediaSourceId){window.removeEventListener("message",onIFrameCallback);var sourceId=event.data.chromeMediaSourceId;if(DetectRTC.screen.sourceId=sourceId,DetectRTC.screen.chromeMediaSource="desktop","PermissionDeniedError"==sourceId){var mediaStreamError={message:"https:"==location.protocol?"User denied to share content of his screen.":SCREEN_COMMON_FAILURE,name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.sourceId=null,connection.onMediaError(mediaStreamError)}captureUserMedia(callback,_session)}event.data&&event.data.chromeExtensionStatus&&(warn("Screen capturing extension status is:",event.data.chromeExtensionStatus),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0))}function _captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed){if(connection.onstatechange({userid:"browser",extra:{},name:"fetching-usermedia",reason:"About to capture user-media with constraints: "+toStr(forcedConstraints)}),connection.preventSSLAutoAllowed&&!dontPreventSSLAutoAllowed&&isChrome)return navigator.customGetUserMediaBar?void navigator.customGetUserMediaBar(forcedConstraints,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,!0); +},function(){connection.onMediaError({name:"PermissionDeniedError",message:"User denied permission.",constraintName:forcedConstraints,session:session})}):void loadScript(connection.resources.customGetUserMediaBar,function(){_captureUserMedia(forcedConstraints,forcedCallback,isRemoveVideoTracks,dontPreventSSLAutoAllowed)});var mediaConfig={onsuccess:function(stream,returnBack,idInstance,streamid){onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session)},onerror:function(e,constraintUsed){if(isFirefox&&"PERMISSION_DENIED"==e&&(e={message:"",name:"PermissionDeniedError",constraintName:constraintUsed,session:session}),isFirefox&&constraintUsed.video&&constraintUsed.video.mozMediaSource)return mediaStreamError={message:Firefox_Screen_Capturing_Warning,name:e.name||"PermissionDeniedError",constraintName:constraintUsed,session:session},void connection.onMediaError(mediaStreamError);if(isString(e))return connection.onMediaError({message:"Unknown Error",name:e,constraintName:constraintUsed,session:session});if(e.name&&("PermissionDeniedError"==e.name||"DevicesNotFoundError"==e.name)){var mediaStreamError="Either: ";mediaStreamError+="\n Media resolutions are not permitted.",mediaStreamError+="\n Another application is using same media device.",mediaStreamError+="\n Media device is not attached or drivers not installed.",mediaStreamError+="\n You denied access once and it is still denied.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError),isChrome&&(session.audio||session.video)&&DetectRTC.load(function(){session.audio&&!DetectRTC.hasMicrophone&&(warn("It seems that you have no microphone attached to your device/system."),session.audio=session.audio=!1,session.video||(alert("It seems that you are capturing microphone and there is no device available or access is denied. Reloading..."),location.reload())),session.video&&!DetectRTC.hasWebcam&&(warn("It seems that you have no webcam attached to your device/system."),session.video=session.video=!1,session.audio||(alert("It seems that you are capturing webcam and there is no device available or access is denied. Reloading..."),location.reload())),DetectRTC.hasMicrophone||DetectRTC.hasWebcam?connection.getUserMediaPromptedOnce||(connection.getUserMediaPromptedOnce=!0,captureUserMedia(callback,session)):(alert("It seems that either both microphone/webcam are not available or access is denied. Reloading..."),location.reload())})}if(e.name&&"ConstraintNotSatisfiedError"==e.name){var mediaStreamError="Either: ";mediaStreamError+="\n You are prompting unknown media resolutions.",mediaStreamError+="\n You are using invalid media constraints.",e.message&&e.message.length&&(mediaStreamError+="\n "+e.message),mediaStreamError={message:mediaStreamError,name:e.name,constraintName:constraintUsed,session:session},connection.onMediaError(mediaStreamError)}session.screen&&(isFirefox?error(Firefox_Screen_Capturing_Warning):"https:"!==location.protocol?isNodeWebkit||"file:"!=location.protocol&&"http:"!=location.protocol||error("You cannot use HTTP or file protocol for screen capturing. You must either use HTTPs or chrome extension page or Node-Webkit page."):error('Unable to detect actual issue. Maybe "deprecated" screen capturing flag was not set using command line or maybe you clicked "No" button or maybe chrome extension returned invalid "sourceId". Please install chrome-extension: http://bit.ly/webrtc-screen-extension')),currentUserMediaRequest.mutex=!1;var idInstance=JSON.stringify(constraintUsed);currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance]},mediaConstraints:connection.mediaConstraints||{}};mediaConfig.constraints=forcedConstraints||constraints,mediaConfig.connection=connection,getUserMedia(mediaConfig)}var session=_session||connection.session;if(isEmpty(session))return void(callback&&callback());if(connection.dontCaptureUserMedia)return callback();if(isData(session)||!connection.isInitiator&&session.oneway)return connection.attachStreams=[],callback();var constraints={audio:session.audio?{mandatory:{},optional:[{chromeRenderToAssociatedSink:!0}]}:!1,video:!!session.video};if(connection._mediaSources.audio&&constraints.audio.optional.push({sourceId:connection._mediaSources.audio}),connection._mediaSources.audiooutput&&constraints.audio.optional.push({sourceId:connection._mediaSources.audiooutput}),connection._mediaSources.audioinput&&constraints.audio.optional.push({sourceId:connection._mediaSources.audioinput}),connection._mediaSources.video&&(constraints.video={optional:[{sourceId:connection._mediaSources.video}]}),connection._mediaSources.videoinput&&(constraints.video={optional:[{sourceId:connection._mediaSources.videoinput}]}),!session.screen&&!constraints.audio&&!constraints.video)return callback();var screen_constraints={audio:!1,video:{mandatory:{chromeMediaSource:DetectRTC.screen.chromeMediaSource,maxWidth:screen.width>1920?screen.width:1920,maxHeight:screen.height>1080?screen.height:1080},optional:[]}};if(isFirefox&&session.screen){if("https:"!==location.protocol)return error(SCREEN_COMMON_FAILURE);warn(Firefox_Screen_Capturing_Warning),screen_constraints.video={mozMediaSource:"window",mediaSource:"window"},constraints.audio&&(screen_constraints.audio=!0,constraints={}),delete screen_constraints.video.chromeMediaSource}if(session.screen){if(isChrome&&DetectRTC.screen.extensionid!=ReservedExtensionID&&(useCustomChromeExtensionForScreenCapturing=!0),isChrome&&!useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&!DetectRTC.screen.sourceId)return listenEventHandler("message",onIFrameCallback),screenFrame||loadScreenFrame(),void screenFrame.postMessage();if(isChrome&&useCustomChromeExtensionForScreenCapturing&&!dontCheckChromExtension&&"screen"==DetectRTC.screen.chromeMediaSource&&DetectRTC.screen.extensionid)return DetectRTC.screen.extensionid==ReservedExtensionID&&-1==document.domain.indexOf("webrtc-experiment.com")?captureUserMedia(callback,_session,!0):(log("checking if chrome extension is installed."),void DetectRTC.screen.getChromeExtensionStatus(function(status){"installed-enabled"==status&&(DetectRTC.screen.chromeMediaSource="desktop"),captureUserMedia(callback,_session,!0),log("chrome extension is installed?","desktop"==DetectRTC.screen.chromeMediaSource)}));if(isChrome&&useCustomChromeExtensionForScreenCapturing&&"desktop"==DetectRTC.screen.chromeMediaSource&&!DetectRTC.screen.sourceId)return void DetectRTC.screen.getSourceId(function(sourceId){if("PermissionDeniedError"==sourceId){var mediaStreamError={message:"User denied to share content of his screen.",name:"PermissionDeniedError",constraintName:screen_constraints,session:session};return currentUserMediaRequest.mutex=!1,DetectRTC.screen.chromeMediaSource="desktop",connection.onMediaError(mediaStreamError)}return"No-Response"==sourceId?(error("Chrome extension seems not available. Make sure that manifest.json#L16 has valid content-script matches pointing to your URL."),DetectRTC.screen.chromeMediaSource="screen",captureUserMedia(callback,_session,!0)):void captureUserMedia(callback,_session,!0)});isChrome&&"desktop"==DetectRTC.screen.chromeMediaSource&&(screen_constraints.video.mandatory.chromeMediaSourceId=DetectRTC.screen.sourceId);var _isFirstSession=isFirstSession;_captureUserMedia(screen_constraints,constraints.audio||constraints.video?function(){_isFirstSession&&(isFirstSession=!0),_captureUserMedia(constraints,callback)}:callback)}else _captureUserMedia(constraints,callback,session.audio&&!session.video)}function onStreamSuccessCallback(stream,returnBack,idInstance,streamid,forcedConstraints,forcedCallback,isRemoveVideoTracks,screen_constraints,constraints,session){streamid||(streamid=getRandomString()),connection.onstatechange({userid:"browser",extra:{},name:"usermedia-fetched",reason:"Captured user media using constraints: "+toStr(forcedConstraints)}),isRemoveVideoTracks&&(stream=convertToAudioStream(stream)),connection.localStreamids.push(streamid),stream.onended=function(){streamedObject.mediaElement&&!streamedObject.mediaElement.parentNode&&document.getElementById(stream.streamid)&&(streamedObject.mediaElement=document.getElementById(stream.streamid)),connection.attachStreams.forEach(function(_stream,index){_stream==stream&&(delete connection.attachStreams[index],connection.attachStreams=swap(connection.attachStreams))}),onStreamEndedHandler(streamedObject,connection),connection.streams[streamid]&&connection.removeStream(streamid);var _stream=connection.streams[streamid];_stream&&_stream.sockets.length&&_stream.sockets.forEach(function(socket){socket.send({streamid:_stream.streamid,stopped:!0})}),currentUserMediaRequest.mutex=!1,currentUserMediaRequest.streams[idInstance]&&delete currentUserMediaRequest.streams[idInstance],DetectRTC.screen.sourceId=null},isIE||(stream.streamid=streamid,stream.isScreen=forcedConstraints==screen_constraints,stream.isVideo=forcedConstraints==constraints&&!!constraints.video,stream.isAudio=forcedConstraints==constraints&&!!constraints.audio&&!constraints.video,stream.preMuted={audio:stream.getAudioTracks().length&&!stream.getAudioTracks()[0].enabled,video:stream.getVideoTracks().length&&!stream.getVideoTracks()[0].enabled});var mediaElement=createMediaElement(stream,session);mediaElement.muted=!0;var streamedObject={stream:stream,streamid:streamid,mediaElement:mediaElement,blobURL:mediaElement.mozSrcObject?URL.createObjectURL(stream):mediaElement.src,type:"local",userid:connection.userid,extra:connection.extra,session:session,isVideo:!!stream.isVideo,isAudio:!!stream.isAudio,isScreen:!!stream.isScreen,isInitiator:!!connection.isInitiator,rtcMultiConnection:connection};isFirstSession&&connection.attachStreams.push(stream),isFirstSession=!1,connection.streams[streamid]=connection._getStream(streamedObject),returnBack||connection.onstream(streamedObject),connection.setDefaultEventsForMediaElement&&connection.setDefaultEventsForMediaElement(mediaElement,streamid),forcedCallback&&forcedCallback(stream,streamedObject),connection.onspeaking&&initHark({stream:stream,streamedObject:streamedObject,connection:connection})}var rtcMultiSession,connection=this;connection.channel=channel||RMCDefaultChannel,connection.isAcceptNewSession=!0,connection.open=function(args){connection.isAcceptNewSession=!1,connection.isInitiator=!0;var dontTransmit=!1;return args&&(isString(args)?connection.sessionid=args:(isNull(args.transmitRoomOnce)||(connection.transmitRoomOnce=args.transmitRoomOnce),isNull(args.dontTransmit)||(dontTransmit=args.dontTransmit),isNull(args.sessionid)||(connection.sessionid=args.sessionid))),connection.socket&&connection.socket.remove&&connection.socket.remove(),connection.sessionid||(connection.sessionid=connection.channel),connection.sessionDescription={sessionid:connection.sessionid,userid:connection.userid,session:connection.session,extra:connection.extra},connection.sessionDescriptions[connection.sessionDescription.sessionid]||(connection.numberOfSessions++,connection.sessionDescriptions[connection.sessionDescription.sessionid]=connection.sessionDescription),initRTCMultiSession(function(){rtcMultiSession.captureUserMediaOnDemand=args?!!args.captureUserMediaOnDemand:!1,args&&args.onMediaCaptured&&(connection.onMediaCaptured=args.onMediaCaptured),rtcMultiSession.captureUserMediaOnDemand||captureUserMedia(function(){rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit}),invokeMediaCaptured(connection)}),rtcMultiSession.captureUserMediaOnDemand&&rtcMultiSession.initSession({sessionDescription:connection.sessionDescription,dontTransmit:dontTransmit})}),connection.sessionDescription},connection.connect=function(sessionid){return sessionid&&(connection.sessionid=sessionid),initRTCMultiSession(function(){log("Signaling channel is ready.")}),this},connection.join=joinSession,connection.send=function(data,_channel){if(connection.numberOfConnectedUsers<=0)return void setTimeout(function(){connection.send(data,_channel)},1e3);if(!data)throw"No file, data or text message to share.";if(data instanceof Array&&!isNull(data[0].size)&&!isNull(data[0].type))for(var i=0;i=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor")>0,isChrome=!!window.chrome&&!isOpera,isIE=!!document.documentMode,isPluginRTC=isSafari||isIE,isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isNodeWebkit=!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]);window.MediaStream=window.MediaStream||window.webkitMediaStream,window.AudioContext=window.AudioContext||window.webkitAudioContext;var chromeVersion=50,matchArray=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);isChrome&&matchArray&&matchArray[2]&&(chromeVersion=parseInt(matchArray[2],10));var firefoxVersion=50;matchArray=navigator.userAgent.match(/Firefox\/(.*)/),isFirefox&&matchArray&&matchArray[1]&&(firefoxVersion=parseInt(matchArray[1],10));var console=window.console||{log:function(){},error:function(){},warn:function(){}};if(isChrome||isFirefox||isSafari)var log=console.log.bind(console),error=console.error.bind(console),warn=console.warn.bind(console);var screenFrame,loadedScreenFrame,loadedIceFrame,onStreamEndedHandlerFiredFor={},onLeaveHandlerFiredFor={},Firefox_Screen_Capturing_Warning='Make sure that you are using Firefox Nightly and you enabled: media.getusermedia.screensharing.enabled flag from about:config page. You also need to add your domain in "media.getusermedia.screensharing.allowed_domains" flag. If you are using WinXP then also enable "media.getusermedia.screensharing.allow_on_old_platforms" flag. NEVER forget to use "only" HTTPs for screen capturing!',SCREEN_COMMON_FAILURE="HTTPs i.e. SSL-based URI is mandatory to use screen capturing.",ReservedExtensionID="ajhifddimkapgcifgcodmmfdlknahffk",useCustomChromeExtensionForScreenCapturing=-1!=document.domain.indexOf("webrtc-experiment.com");attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var Plugin=window.PluginRTC||{};window.onPluginRTCInitialized=function(pluginRTCObject){Plugin=pluginRTCObject,MediaStreamTrack=Plugin.MediaStreamTrack,RTCPeerConnection=Plugin.RTCPeerConnection,RTCIceCandidate=Plugin.RTCIceCandidate,RTCSessionDescription=Plugin.RTCSessionDescription,log(isPluginRTC?"Java-Applet":"ActiveX","plugin has been loaded.")},isEmpty(Plugin)||window.onPluginRTCInitialized(Plugin),isPluginRTC&&loadScript("https://cdn.webrtc-experiment.com/Plugin.EveryWhere.js");var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop()}),this.getVideoTracks().forEach(function(track){track.stop()})});var RTCPeerConnection,defaultConstraints={mandatory:{},optional:[]},currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]},RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate;"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection?RTCPeerConnection=webkitRTCPeerConnection:"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:console.error("WebRTC 1.0 (RTCPeerConnection) API seems NOT available in this browser.");var FileSaver={SaveToDisk:invokeSaveAsDialog},TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,_channel),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection;if(config.text instanceof ArrayBuffer||config.text instanceof DataView)return config.channel.send(config.text,config._channel);var channel=config.channel,_channel=config._channel,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}};!function(){"use strict";function getBrowserInfo(){var nameOffset,verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),browserName=navigator.appName,fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return-1!==(verOffset=nAgt.indexOf("Opera"))?(browserName="Opera",fullVersion=nAgt.substring(verOffset+6),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("MSIE"))?(browserName="IE",fullVersion=nAgt.substring(verOffset+5)):-1!==(verOffset=nAgt.indexOf("Chrome"))?(browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):-1!==(verOffset=nAgt.indexOf("Safari"))?(browserName="Safari",fullVersion=nAgt.substring(verOffset+7),-1!==(verOffset=nAgt.indexOf("Version"))&&(fullVersion=nAgt.substring(verOffset+8))):-1!==(verOffset=nAgt.indexOf("Firefox"))?(browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName)),isEdge&&(browserName="Edge",fullVersion=parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2],10)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName}}function DetectLocalIPAddress(callback){getIPs(function(ip){callback(ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)?"Local: "+ip:"Public: "+ip)})}function getIPs(callback){function handleCandidate(candidate){var ipRegex=/([0-9]{1,3}(\.[0-9]{1,3}){3})/,ipAddress=ipRegex.exec(candidate)[1];void 0===ipDuplicates[ipAddress]&&callback(ipAddress),ipDuplicates[ipAddress]=!0}var ipDuplicates={},RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,useWebKit=!!window.webkitRTCPeerConnection;if(!RTCPeerConnection){var iframe=document.getElementById("iframe");if(!iframe)throw"NOTE: you need to have an iframe in the page right above the script tag.";var win=iframe.contentWindow;RTCPeerConnection=win.RTCPeerConnection||win.mozRTCPeerConnection||win.webkitRTCPeerConnection,useWebKit=!!win.webkitRTCPeerConnection}var servers,mediaConstraints={optional:[{RtpDataChannels:!0}]};useWebKit&&(servers={iceServers:[{urls:"stun:stun.services.mozilla.com"}]},"undefined"!=typeof DetectRTC&&DetectRTC.browser.isFirefox&&DetectRTC.browser.version<=38&&(servers[0]={url:servers[0].urls}));var pc=new RTCPeerConnection(servers,mediaConstraints);pc.onicecandidate=function(ice){ice.candidate&&handleCandidate(ice.candidate.candidate)},pc.createDataChannel(""),pc.createOffer(function(result){pc.setLocalDescription(result,function(){},function(){})},function(){}),setTimeout(function(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){0===line.indexOf("a=candidate:")&&handleCandidate(line)})},1e3)}function checkDeviceSupport(callback){return!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),navigator.enumerateDevices?(MediaDevices=[],void navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)device[d]=_device[d];var skip;MediaDevices.forEach(function(d){d.id===device.id&&(skip=!0)}),skip||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label||(device.label="Please invoke getUserMedia once.",isHTTPs||(device.label="HTTPs is required to get label of this "+device.kind+" device.")),("audioinput"===device.kind||"audio"===device.kind)&&(hasMicrophone=!0),"audiooutput"===device.kind&&(hasSpeakers=!0),("videoinput"===device.kind||"video"===device.kind)&&(hasWebcam=!0),MediaDevices.push(device))}),"undefined"!=typeof DetectRTC&&(DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam),callback&&callback()})):void(callback&&callback())}var navigator=window.navigator;navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){navigator.mediaDevices.enumerateDevices().then(callback)}),"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){}};var isMobileDevice=!!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i),isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),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()},getOsName:function(){var osName="Unknown OS";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS";isMobile.any()?osName=isMobile.getOsName():(-1!==navigator.appVersion.indexOf("Win")&&(osName="Windows"),-1!==navigator.appVersion.indexOf("Mac")&&(osName="MacOS"),-1!==navigator.appVersion.indexOf("X11")&&(osName="UNIX"),-1!==navigator.appVersion.indexOf("Linux")&&(osName="Linux"));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0)});var MediaDevices=[],canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=canEnumerate,hasSpeakers=canEnumerate,hasWebcam=canEnumerate;checkDeviceSupport();var DetectRTC={};DetectRTC.browser=getBrowserInfo(),DetectRTC.browser["is"+DetectRTC.browser.name]=!0;var isHTTPs="https:"===location.protocol,isWebRTCSupported=(!!(window.process&&"object"==typeof window.process&&window.process.versions&&window.process.versions["node-webkit"]),!1);["webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34&&(isScreenCapturingSupported=!0),isHTTPs||(isScreenCapturingSupported=!1),DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported&&webAudio.isCreateMediaStreamSourceSupported||item in window&&(webAudio.isSupported=!0,"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;if(DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice,DetectRTC.isWebSocketsSupported="WebSocket"in window&&2===window.WebSocket.CLOSING,DetectRTC.isWebSocketsBlocked="Checking",DetectRTC.isWebSocketsSupported){var websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,DetectRTC.loadCallback&&DetectRTC.loadCallback()},websocket.onerror=function(){DetectRTC.isWebSocketsBlocked=!0,DetectRTC.loadCallback&&DetectRTC.loadCallback()}}var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=47&&!isHTTPs&&(DetectRTC.isGetUserMediaSupported="Requires HTTPs"),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported,DetectRTC.osName=osName,DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,DetectRTC.DetectLocalIPAddress=DetectLocalIPAddress,DetectRTC.load=function(callback){this.loadCallback=callback,checkDeviceSupport(callback)},DetectRTC.MediaDevices=MediaDevices,DetectRTC.hasMicrophone=hasMicrophone,DetectRTC.hasSpeakers=hasSpeakers,DetectRTC.hasWebcam=hasWebcam;var isSetSinkIdSupported=!1;"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported; +var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,window.DetectRTC=DetectRTC}();var screenCallback;DetectRTC.screen={chromeMediaSource:"screen",extensionid:ReservedExtensionID,getSourceId:function(callback){function onstatus(status){return"installed-enabled"==status?(screenCallback=callback,void window.postMessage("get-sourceId","*")):(DetectRTC.screen.chromeMediaSource="screen",void callback("No-Response"))}if(!callback)throw'"callback" parameter is mandatory.';DetectRTC.screen.status?onstatus(DetectRTC.screen.status):DetectRTC.screen.getChromeExtensionStatus(onstatus)},onMessageCallback:function(data){if(isString(data)||data.sourceId){if(log("chrome message",data),"PermissionDeniedError"==data){if(DetectRTC.screen.chromeMediaSource="PermissionDeniedError",screenCallback)return screenCallback("PermissionDeniedError");throw new Error("PermissionDeniedError")}"rtcmulticonnection-extension-loaded"==data&&(DetectRTC.screen.chromeMediaSource="desktop",DetectRTC.screen.onScreenCapturingExtensionAvailable&&(DetectRTC.screen.onScreenCapturingExtensionAvailable(),DetectRTC.screen.onScreenCapturingExtensionAvailable=null)),data.sourceId&&(DetectRTC.screen.sourceId=data.sourceId,screenCallback&&screenCallback(DetectRTC.screen.sourceId))}},getChromeExtensionStatus:function(extensionid,callback){function _callback(status){DetectRTC.screen.status=status,callback(status)}if(isFirefox)return _callback("not-chrome");2!=arguments.length&&(callback=extensionid,extensionid=this.extensionid);var image=document.createElement("img");image.src="chrome-extension://"+extensionid+"/icon.png",image.onload=function(){DetectRTC.screen.chromeMediaSource="screen",window.postMessage("are-you-there","*"),setTimeout(function(){_callback("screen"==DetectRTC.screen.chromeMediaSource?"desktop"==DetectRTC.screen.chromeMediaSource?"installed-enabled":"installed-disabled":"installed-enabled")},2e3)},image.onerror=function(){_callback("not-installed")}}},window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.addEventListener("message",function(event){event.origin==window.location.origin&&DetectRTC.screen.onMessageCallback(event.data)})}(); \ No newline at end of file diff --git a/RTCMultiConnection/v2.2.2/demos/RecordRTC-and-RTCMultiConnection.html b/RTCMultiConnection/v2.2.2/demos/RecordRTC-and-RTCMultiConnection.html index cf74ef37..fe911082 100755 --- a/RTCMultiConnection/v2.2.2/demos/RecordRTC-and-RTCMultiConnection.html +++ b/RTCMultiConnection/v2.2.2/demos/RecordRTC-and-RTCMultiConnection.html @@ -1,7 +1,7 @@ - @@ -14,7 +14,7 @@ - + @@ -312,6 +312,7 @@

    +
  1. You can record both local and remote audio+video streams in chrome >= 50
  2. You can record video from both local and remote media streams (chrome,firefox,opera)
  3. You can record audio from local stream (chrome,firefox,opera)
  4. You can record "remote-audio" only on firefox
  5. @@ -322,15 +323,18 @@

    You can invoke audio/video recorder like this:

    -                connection.streams['stream-id'].startRecording({ audio: true, video: true });
    -            
    +connection.streams['stream-id'].startRecording({ + audio: true, + video: true +}); +

You can skip argument:

-                // to record both audio and video connection.streams['stream-id'].
-                startRecording();
-            
+// to record both audio and video +connection.streams['stream-id'].startRecording(); +

Stop recording and get blob.

@@ -341,25 +345,45 @@

Stop recording and get blob.

-                connection.streams['stream-id'].stopRecording(function (blob) { var mediaElement = document.createElement('audio'); mediaElement.src = URL.createObjectURL(blob.audio); document.documentElement.appendChild(h2); });
-            
+connection.streams['stream-id'].stopRecording(function (blob) { + var mediaElement = document.createElement('audio'); + mediaElement.src = URL.createObjectURL(blob.audio); + document.documentElement.appendChild(h2); +}); +

You can force to stop a specific stream:

-
-                connection.streams['stream-id'].stopRecording(onBlobs, { audio: true // stop audio recorder and get audio blob });
-            
+
+connection.streams['stream-id'].stopRecording(onBlobs, { 
+    audio: true // stop audio recorder and get audio blob 
+});
+

A simple working example:

-                connection.onstream = function (e) { // e.type == 'remote' || 'local' connection.streams[e.streamid].
-
-                startRecording({ video: true }); // record 10 sec audio/video var recordingInterval = 10 * 10000; setTimeout(function () { connection.streams[e.streamid].
-                stopRecording(function (blob) { var mediaElement = document.createElement('video'); mediaElement.src = URL.createObjectURL(blob.video); document.documentElement.appendChild(h2); }); }, recordingInterval) }
-            
+connection.onstream = function (e) { + + // e.type == 'remote' || 'local' + connection.streams[e.streamid].startRecording({ + video: true + }); + + // record 10 sec audio/video + var recordingInterval = 10 * 10000; + + setTimeout(function () { + connection.streams[e.streamid].stopRecording(function (blob) { + var mediaElement = document.createElement('video'); + mediaElement.src = URL.createObjectURL(blob.video); + document.documentElement.appendChild(h2); + }); + }, recordingInterval) +}; +
@@ -387,9 +411,10 @@

@WebRTCWeb - - + \ No newline at end of file diff --git a/RTCMultiConnection/v2.2.2/demos/all-in-one.html b/RTCMultiConnection/v2.2.2/demos/all-in-one.html index eda1862d..700e33d2 100755 --- a/RTCMultiConnection/v2.2.2/demos/all-in-one.html +++ b/RTCMultiConnection/v2.2.2/demos/all-in-one.html @@ -14,7 +14,7 @@ - + @@ -110,8 +110,8 @@

Muaz Khan . @WebRTCWeb . Github . - Latest issues . - What's New? + Latest issues . + What's New?

@@ -412,41 +412,96 @@

Share Files

How to use RTCMultiConnection?

-                // https://cdn.webrtc-experiment.com/RTCMultiConnection.js
-            
+// https://cdn.webrtc-experiment.com/RTCMultiConnection.js +

Common Code

-                var MODERATOR_CHANNEL_ID = 'ABCDEF'; // channel-id var MODERATOR_SESSION_ID = 'XYZ'; // room-id var MODERATOR_ID = 'JKL'; // user-id var MODERATOR_SESSION = { // media-type audio: true, video: true }; var MODERATOR_EXTRA = {}; // empty extra-data
-            
+var MODERATOR_CHANNEL_ID = 'ABCDEF'; // channel-id +var MODERATOR_SESSION_ID = 'XYZ'; // room-id +var MODERATOR_ID = 'JKL'; // user-id +var MODERATOR_SESSION = { // media-type + audio: true, + video: true +}; +var MODERATOR_EXTRA = {}; // empty extra-data +

Code for Room Moderator (i.e. Initiator)

-                var moderator = new RTCMultiConnection(MODERATOR_CHANNEL_ID); moderator.session = MODERATOR_SESSION; moderator.userid = MODERATOR_ID; moderator.extra = MODERATOR_EXTRA; moderator.open({ dontTransmit: true, sessionid: MODERATOR_SESSION_ID });
-
-            
+var moderator = new RTCMultiConnection(MODERATOR_CHANNEL_ID); +moderator.session = MODERATOR_SESSION; +moderator.userid = MODERATOR_ID; +moderator.extra = MODERATOR_EXTRA; +moderator.open({ + dontTransmit: true, + sessionid: MODERATOR_SESSION_ID +}); +

Code for Room Participants

-                var participants = new RTCMultiConnection(MODERATOR_CHANNEL_ID); participants.join({ sessionid: MODERATOR_SESSION_ID, userid: MODERATOR_ID, extra: MODERATOR_EXTRA, session: MODERATOR_SESSION });
-            
+var participants = new RTCMultiConnection(MODERATOR_CHANNEL_ID); +participants.join({ + sessionid: MODERATOR_SESSION_ID, + userid: MODERATOR_ID, + extra: MODERATOR_EXTRA, + session: MODERATOR_SESSION +}); +

(optional) Handle how to get streams

-                // same code can be used for participants // (it is optional) moderator.onstreamid = function(event) { // got a clue of incoming remote stream // didn't get remote stream yet var incoming_stream_id = event.streamid; YOUR_PREVIEW_IMAGE.show(); // or YOUR_PREVIEW_VIDEO.show(); }; // same code can be used for participants // it is useful moderator.onstream = function(event) { // got local or remote stream // if(event.type == 'local') {} // if(event.type == 'remote') {} document.body.appendChild(event.mediaElement); // or YOUR_VIDEO.src = event.blobURL; // or YOUR_VIDEO.src = URL.createObjectURL(event.stream); }; // same code can be used for participants // it is useful but optional moderator.onstreamended = function(event) { event.mediaElement.parentNode.removeChild(event.mediaElement); };
-            
+// same code can be used for participants +// (it is optional) +connection.onstreamid = function(event) { + // got a clue of incoming remote stream + // didn't get remote stream yet + + var incoming_stream_id = event.streamid; + + YOUR_PREVIEW_IMAGE.show(); + + // or + YOUR_PREVIEW_VIDEO.show(); +}; + +// same code can be used for participants +// it is useful +connection.onstream = function(event) { + // got local or remote stream + // if(event.type == 'local') {} + // if(event.type == 'remote') {} + + document.body.appendChild(event.mediaElement); + + // or YOUR_VIDEO.src = event.blobURL; + // or YOUR_VIDEO.src = URL.createObjectURL(event.stream); +}; + +// same code can be used for participants +// it is useful but optional +connection.onstreamended = function(event) { + event.mediaElement.parentNode.removeChild(event.mediaElement); +}; + +
+ +
+

Latest Issues

+
@@ -459,7 +514,7 @@

Feedback

-

Latest Updates +

Latest Updates

@@ -475,7 +530,10 @@

- + diff --git a/RTCMultiConnection/v2.2.2/demos/remote-stream-forwarding.html b/RTCMultiConnection/v2.2.2/demos/remote-stream-forwarding.html index e178659d..a1851f5d 100755 --- a/RTCMultiConnection/v2.2.2/demos/remote-stream-forwarding.html +++ b/RTCMultiConnection/v2.2.2/demos/remote-stream-forwarding.html @@ -1,7 +1,7 @@ - @@ -16,7 +16,7 @@ - + @@ -91,8 +91,8 @@

- Chrome is still not supporting remote audio forwarding. It will forward only remote - video stream. There is another demo: WebRTC Scalable Broadcast which can broadcast video/screen over unlimited users! + Please try this demo instead:
+ https://rtcmulticonnection.herokuapp.com/demos/Scalable-Broadcast.html
@@ -225,6 +225,11 @@

How to use?

+
+

Latest Issues

+
+
+

Feedback

@@ -235,7 +240,7 @@

Feedback

-

Latest Updates +

Latest Updates

@@ -251,6 +256,9 @@

+ diff --git a/RTCMultiConnection/v2.2.2/dev/RTCMultiConnection.js b/RTCMultiConnection/v2.2.2/dev/RTCMultiConnection.js index 9df0a1d0..9ff41533 100755 --- a/RTCMultiConnection/v2.2.2/dev/RTCMultiConnection.js +++ b/RTCMultiConnection/v2.2.2/dev/RTCMultiConnection.js @@ -308,6 +308,16 @@ window.RTCMultiConnection = function(channel) { sourceId: connection._mediaSources.audio }); } + if (connection._mediaSources.audiooutput) { + constraints.audio.optional.push({ + sourceId: connection._mediaSources.audiooutput + }); + } + if (connection._mediaSources.audioinput) { + constraints.audio.optional.push({ + sourceId: connection._mediaSources.audioinput + }); + } // if custom video device is selected if (connection._mediaSources.video) { @@ -317,6 +327,13 @@ window.RTCMultiConnection = function(channel) { }] }; } + if (connection._mediaSources.videoinput) { + constraints.video = { + optional: [{ + sourceId: connection._mediaSources.videoinput + }] + }; + } // for connection.session = {}; if (!session.screen && !constraints.audio && !constraints.video) { @@ -341,10 +358,10 @@ window.RTCMultiConnection = function(channel) { } warn(Firefox_Screen_Capturing_Warning); - screen_constraints.video = merge(screen_constraints.video.mandatory, { + screen_constraints.video = { mozMediaSource: 'window', // mozMediaSource is redundant here mediaSource: 'window' // 'screen' || 'window' - }); + }; // Firefox is supporting audio+screen from single getUserMedia request // audio+video+screen will become audio+screen for Firefox diff --git a/RTCMultiConnection/v2.2.2/dev/setDefaults.js b/RTCMultiConnection/v2.2.2/dev/setDefaults.js index 2c2a8d9a..9303fcb7 100755 --- a/RTCMultiConnection/v2.2.2/dev/setDefaults.js +++ b/RTCMultiConnection/v2.2.2/dev/setDefaults.js @@ -689,7 +689,11 @@ function setDefaults(connection) { if (isFirefox) { // firefox supports both audio/video recording in single webm file - if (session.video) { + if (self.stream.getAudioTracks().length && self.stream.getVideoTracks().length) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } else if (session.video) { self.videoRecorder = RecordRTC(self.stream, { type: 'video' }); @@ -699,17 +703,36 @@ function setDefaults(connection) { }); } } else if (isChrome) { - // chrome supports recording in two separate files: WAV and WebM - if (session.video) { + // chrome >= 48 supports MediaRecorder API + // MediaRecorder API can record remote audio+video streams as well! + + if (isMediaRecorderCompatible() && connection.DetectRTC.browser.version >= 50 && self.stream.getAudioTracks().length && self.stream.getVideoTracks().length) { self.videoRecorder = RecordRTC(self.stream, { type: 'video' }); - } + } else if (isMediaRecorderCompatible() && connection.DetectRTC.browser.version >= 50) { + if (session.video) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } else if (session.audio) { + self.audioRecorder = RecordRTC(self.stream, { + type: 'audio' + }); + } + } else { + // chrome supports recording in two separate files: WAV and WebM + if (session.video) { + self.videoRecorder = RecordRTC(self.stream, { + type: 'video' + }); + } - if (session.audio) { - self.audioRecorder = RecordRTC(self.stream, { - type: 'audio' - }); + if (session.audio) { + self.audioRecorder = RecordRTC(self.stream, { + type: 'audio' + }); + } } } diff --git a/Record-Entire-Meeting/Browser-Recording-Helper.js b/Record-Entire-Meeting/Browser-Recording-Helper.js index 6df7c8a4..a89b6932 100755 --- a/Record-Entire-Meeting/Browser-Recording-Helper.js +++ b/Record-Entire-Meeting/Browser-Recording-Helper.js @@ -1,180 +1,98 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.webrtc-experiment.com/licence +// Documentation - github.com/streamproc/MediaStreamRecorder var RecorderHelper = (function() { - var isFirefox = !!navigator.mozGetUserMedia; var socket; // socket.io var roomId; var userId; - var UploadInterval = 2000; // default, 2 seconds + var UploadInterval = 5 * 1000; - // default: 240p - var VideoWidth = 320; - var VideoHeight = 240; + var mediaStreamRecorder; - var multiStreamRecorder; - - function initRecorder(MediaStream, video) { - // listen for "ended" event - // works only in Chrome - MediaStream.addEventListener('ended', function() { - RecorderHelper.StopRecording(); + function initRecorder(mediaStream, video) { + mediaStream.addEventListener('ended', function() { + // RecorderHelper.StopRecording(); }, false); - multiStreamRecorder = new MultiStreamRecorder(MediaStream); - - // resolutions are passed here - multiStreamRecorder.canvas = { - width: VideoWidth, - height: VideoHeight - }; - - // LET Chrome decide "best" buffer-size value - // todo???? YOU can use HARD-CODED value here - // need to use bufferSize=16384 when recording 720p to make sure audio is NOT affected. - multiStreamRecorder.bufferSize = 0; - - // RECORD only LEFT channel - // https://github.com/streamproc/MediaStreamRecorder#audiochannelss - multiStreamRecorder.audioChannels = 1; - - // HTMLVideoElement - // https://github.com/streamproc/MediaStreamRecorder#video - multiStreamRecorder.video = video; + mediaStreamRecorder = new MediaStreamRecorder(mediaStream); + mediaStreamRecorder.mimeType = 'video/webm'; - multiStreamRecorder.ondataavailable = function(blobs) { - // WAV/WebM blobs - onDataAvailable(blobs.audio, blobs.video); + mediaStreamRecorder.ondataavailable = function(blobs) { + onDataAvailable(blobs); }; - multiStreamRecorder.start(UploadInterval); + mediaStreamRecorder.start(UploadInterval); socket.on('complete', function(fileName) { RecorderHelper.OnComplete(fileName); }); - } - // this variable is used to detect if all pending are uploaded - var isStopPending = false; + socket.on('ffmpeg-progress', function(response) { + RecorderHelper.OnProgress(response); + }); + } - // handler to detect if user is leaving window.addEventListener('beforeunload', function(event) { - if (multiStreamRecorder) { - // this code quickly stops recording - // in first confirm-box - multiStreamRecorder.stop(); - multiStreamRecorder = null; + if (mediaStreamRecorder) { + mediaStreamRecorder.stop(); + mediaStreamRecorder = null; } - // if some of the pending blobs are still there if (Object.keys(socketPendingMessages).length) { - isStopPending = true; event.returnValue = 'Still some recording intervals are pending.'; - } else { - // otherwise, let server know that stream is finally stopped. - socket.emit('stream-stopped'); } }, false); - function onDataAvailable(audioBlob, videoBlob) { - // todo: DataURL process is too slow. - // UPload ArrayBuffer or Blob to the server. - - // DataURL is uploaded to server instead of Blobs - // This process is 60-80 times slow comparing direct blob uploading - // Because we need to wait for FileReader to read DataURLs - getDataURL(audioBlob, function(audioDataURL) { - // this object containers audio DataURL - var audio = { - blob: audioBlob, - dataURL: audioDataURL + function onDataAvailable(blob) { + getDataURL(blob, function(dataURL) { + var data = { + blob: blob, + dataURL: dataURL }; - // Firefox will be MERELY having single audio DataURL - // that DataURL is actually WebM file containing both audio and video - if (isFirefox) { - postFiles(audio); - return; - } - - // read video DataURL for Chrome - getDataURL(videoBlob, function(videoDataURL) { - // this object contains video DataURL - var video = { - blob: videoBlob, - dataURL: videoDataURL - }; - - // upload both audio and video DataURLs to server - postFiles(audio, video); - }); + postFiles(data); }); } - // each user is having unique file-name-string - // this is exactly name that is stored on server like this: - // file-1.wav - // file-1.webm - // file-2.wav - // file-2.webm var fileNameString; var index = 1; - function postFiles(audio, video) { + function postFiles(data) { var interval = index; - // exact file name that is uploaded to server fileName = fileNameString + '-' + index; - index++; // increment interval + index++; - // single object that contains both audio/video DataURls var files = { - interval: interval, // currently uploading interval - isFirefox: !!isFirefox, - roomId: roomId || generatefileNameString(), // unique roomid - userId: userId || generatefileNameString(), // unique userid - fileName: fileNameString // file-name-string + interval: interval, + roomId: roomId || generatefileNameString(), + userId: userId || generatefileNameString(), + fileName: fileNameString }; - // audio DataURL - files.audio = { - name: fileName + '.' + audio.blob.type.split('/')[1], - type: audio.blob.type, - contents: audio.dataURL, + files.data = { + name: fileName + '.' + data.blob.type.split('/')[1], + type: data.blob.type, + contents: data.dataURL, interval: interval }; - if (!isFirefox) { - // video DataURL for Chrome - files.video = { - name: fileName + '.' + video.blob.type.split('/')[1], - type: video.blob.type, - contents: video.dataURL, - interval: interval - }; - } - - // if socket.io is in progress if (isSocketBusy) { - // store recordings in a global object - // it is named as "pending-data" socketPendingMessages[interval] = { - files: files, // the actual pending data - - // this method is invoke to upload "pending-data" + files: files, emit: function() { isSocketBusy = true; console.info('emitting', interval); - // uploading to server socket.emit('recording-message', JSON.stringify(files), function() { isSocketBusy = false; - // if there are still some pending-data if (socketPendingMessages[interval + 1]) { socketPendingMessages[interval + 1].emit(); delete socketPendingMessages[interval + 1]; - } else if (isStopPending) { - // otherwise, let server know that all pending DataURLs are uploaded + } else if(!mediaStreamRecorder) { socket.emit('stream-stopped'); } }); @@ -186,28 +104,23 @@ var RecorderHelper = (function() { isSocketBusy = true; console.info('emitting', interval); - // uploading to server socket.emit('recording-message', JSON.stringify(files), function() { isSocketBusy = false; - // if there are still some pending-data + console.info('emitting', interval); + if (socketPendingMessages[interval + 1]) { socketPendingMessages[interval + 1].emit(); delete socketPendingMessages[interval + 1]; - } else if (isStopPending) { - // otherwise, let server know that all pending DataURLs are uploaded + } else if(!mediaStreamRecorder) { socket.emit('stream-stopped'); } }); } - // this global variable is used to detect if socket.io is busy var isSocketBusy = false; - - // this global object stores all pending-blobs var socketPendingMessages = {}; - // simply generates random string function generatefileNameString() { if (window.crypto) { var a = window.crypto.getRandomValues(new Uint32Array(3)), @@ -219,9 +132,6 @@ var RecorderHelper = (function() { } } - // reads DataURL in WebWorker - // todo: single WebWorker can be used to read all recordings - // it'll reduce RAM usage as well as speed-up reading process (a bit). function getDataURL(blob, callback) { if (!!window.Worker) { var webWorker = processInWebWorker(function readFile(_blob) { @@ -244,7 +154,6 @@ var RecorderHelper = (function() { var worker; - // this function generates WebWorker code on the fly. function processInWebWorker(_function) { if (worker) { return worker; @@ -262,46 +171,36 @@ var RecorderHelper = (function() { } return { - // public API - // RecorderHelper.StartRecording(config); StartRecording: function(obj) { index = 1; - // make sure that file name is uniqe for each user - fileNameString = /* obj.FileName || */ generatefileNameString(); + fileNameString = obj.FileName || generatefileNameString(); - roomId = obj.roomId; // getting value from config - userId = obj.userId; // getting value from config - UploadInterval = obj.UploadInterval; // getting value from config - VideoWidth = obj.VideoWidth; // getting value from config - VideoHeight = obj.VideoHeight; // getting value from config + roomId = obj.roomId; + userId = obj.userId; + UploadInterval = obj.UploadInterval; - socket = obj.Socket; // getting value from config + socket = obj.Socket; - // Starting Recording initRecorder(obj.MediaStream, obj.HTMLVideoElement); this.alreadyStopped = false; }, StopRecording: function() { - if(this.alreadyStopped) return; + if (this.alreadyStopped) return; this.alreadyStopped = true; - // LET server know recording process is stopped - // todo???? maybe ask server to wait for 5-10 minutes - // then invoke merge/concatenate functions?? - - socket.emit('stream-stopped'); - multiStreamRecorder.stop(); - multiStreamRecorder = null; - // socket.disconnect(); - - // location.reload(); + mediaStreamRecorder.stop(); + mediaStreamRecorder = null; }, OnComplete: function(fileName) { console.debug('File saved at: /uploads/' + roomId + '/' + fileName); + }, + + OnProgress: function(response) { + console.info('ffmpeg progress', response.progress, response); } }; -})(); \ No newline at end of file +})(); diff --git a/Record-Entire-Meeting/Concatenate-Recordings.js b/Record-Entire-Meeting/Concatenate-Recordings.js index b35c87c5..93e26bbd 100755 --- a/Record-Entire-Meeting/Concatenate-Recordings.js +++ b/Record-Entire-Meeting/Concatenate-Recordings.js @@ -1,233 +1,86 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.webrtc-experiment.com/licence +// Documentation - github.com/streamproc/MediaStreamRecorder var exec = require('child_process').exec; var fs = require('fs'); -var isWindows = !!process.platform.match(/^win/); var socket; module.exports = exports = function(files, _socket) { socket = _socket; - if(isWindows) { - concatenateInWindows(files); - return; - } + console.log('concatenating files in room:', files.roomId, ' for user:', files.userId, ' at interval:', files.interval); + concatenateInLinuxOrMac(files); }; -// concatenate-all-blobs in linux -function concatenateInLinuxOrMac(files, times, fileNotExistsTries) { - console.log('dev-logs', 'Concatenating all WebM files from recording interval ' + files.interval + '.'); +var ffmpeg = require('fluent-ffmpeg'); - console.log('files', files.fileName); - - // room-directory +function concatenateInLinuxOrMac(files) { var uploadsFolder = __dirname + '/uploads/' + files.roomId + '/'; - // "lastIndex" is currently passed via "disconnect" event var lastIndex = files.lastIndex; - // ffmpeg command (currently) only works via a TEXT file - var mergelist = ''; + var allFiles = []; var isAnySingleFileStillInProgress = false; for (var i = 1; i < lastIndex; i++) { - // if file doesn't exists & wait for it to be created - // todo????? - if (!fs.existsSync(uploadsFolder + files.fileName + '-' + i + "-merged.webm")) { + if (!fs.existsSync(uploadsFolder + files.fileName + '-' + i + ".webm")) { isAnySingleFileStillInProgress = true; i = lastIndex; - console.log('dev-logs', 'Concatenator is waiting for ' + files.fileName + '-' + i + '-merged.webm in recording interval ' + files.interval + '.'); break; } - mergelist += "file '" + uploadsFolder + files.fileName + '-' + i + "-merged.webm'\n"; - - console.log('-------------------------------'); - console.log("file '" + uploadsFolder + files.fileName + '-' + i + "-merged.webm'\n"); - console.log('-------------------------------'); + allFiles.push(uploadsFolder + files.fileName + '-' + i + '.webm'); } if (isAnySingleFileStillInProgress) { - /* - console.log('-------------------------------'); console.log('isAnySingleFileStillInProgress'); - console.log('-------------------------------'); setTimeout(function() { - fileNotExistsTries = fileNotExistsTries || 0; - fileNotExistsTries++; - - if (fileNotExistsTries > 5) { - fileNotExistsTries = 0; - files.lastIndex -= 1; - } - concatenateInLinuxOrMac(files, times, fileNotExistsTries); + concatenateInLinuxOrMac(files); }, 2000); return; - */ } - mergelist = mergelist.substr(0, mergelist.length - 1); - - // this TEXT file is used to be invoked in ffmpeg - // ffmpeg reads it, and merges all files accordingly - var mergelist_file_txt = uploadsFolder + files.fileName + '-mergelist.txt'; + // ffmpeg -y -i video.webm -i screen.webm -filter_complex "[0:v]setpts=PTS-STARTPTS, pad=iw:ih[bg]; [1:v]scale=320:240,setpts=PTS-STARTPTS[fg]; [bg][fg]overlay=main_w-overlay_w-10:main_h-overlay_h-10" fullRecording.webm + // ffmpeg -y -i 6354797637490482-1.webm -i 6354797637490482-2.webm fullRecording.webm - console.log('----------------------------'); - console.log(mergelist_file_txt); - console.log(mergelist); - console.log('----------------------------'); - - // if TEXT file already exists, remove it. - if (fs.existsSync(mergelist_file_txt)) { - fs.unlink(mergelist_file_txt); - } + console.log('executing ffmpeg command'); - // write TEXT file and wait for success-callback - fs.writeFile(mergelist_file_txt, mergelist, function(err) { - if (err) { - console.log('dev-logs', err); - - // todo?????if it fails? - concatenateInLinuxOrMac(files, times, fileNotExistsTries); - } else { - var final_file = uploadsFolder + files.userId + '.webm'; - var command = 'ffmpeg -f concat -i ' + mergelist_file_txt + ' -c copy ' + final_file; - - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - // if failed to concatenate then try-again-and-again until 30 minutes - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - concatenateInLinuxOrMac(files, times); - }, 1000); - } else { - console.log('dev-logs', 'Successfully concatenated all WebM files from recording interval ' + files.interval + '.'); - - // remove TEXT file - fs.unlink(mergelist_file_txt); - - // remove all user files - unlink_merged_files(uploadsFolder + files.fileName, lastIndex); - - // tell browser is file is successfully uploaded. - socket.emit('complete', files.userId + '.webm'); - - // if requested: - // var ScaleRecordings = require('./Scale-Recordings.js'); - // ScaleRecordings(files, times); - } - }); + var ffmpegCommand = ffmpeg(allFiles[0]); + allFiles.forEach(function(filePath, idx) { + if (idx !== 0) { + ffmpegCommand = ffmpegCommand.input(filePath); } }); -} - -// concatenate-all-blobs in windows -function concatenateInWindows(files, times, fileNotExistsTries) { - console.log('dev-logs', 'Concatenating all WebM files from recording interval ' + files.interval + '.'); - - // room-directory - var uploadsFolder = __dirname + '\\uploads\\' + files.roomId + '\\'; - - // "lastIndex" is currently passed via "disconnect" event - var lastIndex = files.lastIndex; - - // ffmpeg command (currently) only works via a TEXT file - var mergelist = ''; - var isAnySingleFileStillInProgress = false; - for (var i = 1; i < lastIndex; i++) { - // if file doesn't exists & wait for it to be created - // todo????? - if (!fs.existsSync(uploadsFolder + files.fileName + '-' + i + "-merged.webm")) { - isAnySingleFileStillInProgress = true; - i = lastIndex; - console.log('dev-logs', 'Concatenator is waiting for ' + files.fileName + '-' + i + '-merged.webm in recording interval ' + files.interval + '.'); - break; - } - mergelist += "file '" + uploadsFolder + files.fileName + '-' + i + "-merged.webm'\n"; - } - - if (isAnySingleFileStillInProgress) { - /* - setTimeout(function() { - fileNotExistsTries = fileNotExistsTries || 0; - fileNotExistsTries++; - - if (fileNotExistsTries > 5) { - fileNotExistsTries = 0; - files.lastIndex -= 1; - } - concatenateInWindows(files, times, fileNotExistsTries); - }, 2000); - return; - */ - } - - mergelist = mergelist.substr(0, mergelist.length - 1); - - // this TEXT file is used to be invoked in ffmpeg - // ffmpeg reads it, and merges all files accordingly - var mergelist_file_txt = uploadsFolder + files.fileName + '-mergelist.txt'; + ffmpegCommand.on('progress', function(progress) { + socket.emit('ffmpeg-progress', { + userId: files.userId, + roomId: files.roomId, + progress: progress + }); + }); - console.log('----------------------------'); - console.log(mergelist_file_txt); - console.log(mergelist); - console.log('----------------------------'); + ffmpegCommand.on('error', function(err) { + console.log(err.message); + }); - // if TEXT file already exists, remove it. - if (fs.existsSync(mergelist_file_txt)) { - fs.unlink(mergelist_file_txt); - } + ffmpegCommand.on('end', function() { + console.log('Successfully concatenated all WebM files from recording interval ' + files.interval + '.'); + socket.emit('complete', files.userId + '.webm'); - // write TEXT file and wait for success-callback - fs.writeFile(mergelist_file_txt, mergelist, function(err) { - if (err) { - // todo?????if it fails? - console.log('dev-logs', err); - - // todo?????if it fails? - concatenateInLinuxOrMac(files, times, fileNotExistsTries); - } else { - // BAT file address - var concatenate_bat = __dirname + '\\bat-files\\concatenate.bat'; - - var final_file = uploadsFolder + files.userId + '.webm'; - var command = concatenate_bat + ', ' + mergelist_file_txt + ' ' + final_file; - - // invoke BAT via BASH - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - // if failed to concatenate then try-again-and-again until 30 minutes - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - concatenateInWindows(files, times); - }, 1000); - } else { - console.log('dev-logs', 'Successfully concatenated all WebM files from recording interval ' + files.interval + '.'); - - // remove TEXT file - fs.unlink(mergelist_file_txt); - - // remove all user files - unlink_merged_files(uploadsFolder + files.fileName, lastIndex); - - /// if requested: - // var ScaleRecordings = require('./Scale-Recordings.js'); - // ScaleRecordings(files, times); - } - }); - } + unlink_merged_files(uploadsFolder + files.fileName, lastIndex); }); + + var final_file = uploadsFolder + files.userId + '.webm'; + ffmpegCommand.mergeToFile(final_file, __dirname + '/temp-uploads/'); } // delete all files from specific user function unlink_merged_files(fileName, lastIndex, index) { + console.log('unlinking redundant files'); + function unlink_file(_index) { - fs.unlink(fileName + '-' + _index + "-merged.webm", function(error) { + fs.unlink(fileName + '-' + _index + ".webm", function(error) { if (error) { setTimeout(function() { unlink_merged_files(fileName, lastIndex, _index); diff --git a/Record-Entire-Meeting/MediaStreamRecorder.js b/Record-Entire-Meeting/MediaStreamRecorder.js index fd918aa7..4ef72abb 100755 --- a/Record-Entire-Meeting/MediaStreamRecorder.js +++ b/Record-Entire-Meeting/MediaStreamRecorder.js @@ -1,133 +1,8 @@ // Muaz Khan - www.MuazKhan.com // MIT License - www.webrtc-experiment.com/licence // Documentation - github.com/streamproc/MediaStreamRecorder - // ______________________ // MediaStreamRecorder.js - -function MediaStreamRecorder(mediaStream) { - if (!mediaStream) throw 'MediaStream is mandatory.'; - - // void start(optional long timeSlice) - // timestamp to fire "ondataavailable" - this.start = function(timeSlice) { - // Media Stream Recording API has not been implemented in chrome yet; - // That's why using WebAudio API to record stereo audio in WAV format - var Recorder = IsChrome ? window.StereoRecorder : window.MediaRecorderWrapper; - - // video recorder (in WebM format) - if (this.mimeType.indexOf('video') != -1) { - Recorder = IsChrome ? window.WhammyRecorder : window.MediaRecorderWrapper; - } - - // video recorder (in GIF format) - if (this.mimeType === 'image/gif') Recorder = window.GifRecorder; - - mediaRecorder = new Recorder(mediaStream); - mediaRecorder.ondataavailable = this.ondataavailable; - mediaRecorder.onstop = this.onstop; - mediaRecorder.onStartedDrawingNonBlankFrames = this.onStartedDrawingNonBlankFrames; - - // Merge all data-types except "function" - mediaRecorder = mergeProps(mediaRecorder, this); - - mediaRecorder.start(timeSlice); - }; - - this.onStartedDrawingNonBlankFrames = function() {}; - this.clearOldRecordedFrames = function() { - if (!mediaRecorder) return; - mediaRecorder.clearOldRecordedFrames(); - }; - - this.stop = function() { - if (mediaRecorder) mediaRecorder.stop(); - }; - - this.ondataavailable = function(blob) { - console.log('ondataavailable..', blob); - }; - - this.onstop = function(error) { - console.warn('stopped..', error); - }; - - // Reference to "MediaRecorder.js" - var mediaRecorder; -} - -// below scripts are used to auto-load required files. - -function loadScript(src, onload) { - var root = window.MediaStreamRecorderScriptsDir; - - var script = document.createElement('script'); - script.src = root + src; - script.onload = onload || function() {}; - document.documentElement.appendChild(script); -} - -// Muaz Khan - www.MuazKhan.com -// MIT License - www.webrtc-experiment.com/licence -// Documentation - github.com/streamproc/MediaStreamRecorder - -// _____________________________ -// Cross-Browser-Declarations.js - -// animation-frame used in WebM recording -if (!window.requestAnimationFrame) { - requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; -} - -if (!window.cancelAnimationFrame) { - cancelAnimationFrame = window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame; -} - -// WebAudio API representer -if (!window.AudioContext) { - window.AudioContext = window.webkitAudioContext || window.mozAudioContext; -} - -URL = window.URL || window.webkitURL; -navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; - -if (window.webkitMediaStream) window.MediaStream = window.webkitMediaStream; - -IsChrome = !!navigator.webkitGetUserMedia; - -// Merge all other data-types except "function" - -function mergeProps(mergein, mergeto) { - mergeto = reformatProps(mergeto); - for (var t in mergeto) { - if (typeof mergeto[t] !== 'function') { - mergein[t] = mergeto[t]; - } - } - return mergein; -} - -function reformatProps(obj) { - var output = {}; - for (var o in obj) { - if (o.indexOf('-') != -1) { - var splitted = o.split('-'); - var name = splitted[0] + splitted[1].split('')[0].toUpperCase() + splitted[1].substr(1); - output[name] = obj[o]; - } else output[o] = obj[o]; - } - return output; -} - -// ______________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 -// ObjectStore.js -var ObjectStore = { - AudioContext: window.AudioContext || window.webkitAudioContext -}; - -// ================ -// MediaRecorder.js - /** * Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html * The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts, @@ -138,12 +13,11 @@ var ObjectStore = { * When the recorder starts, it creates a "Media Encoder" thread to read data from MediaEncoder object and store buffer in EncodedBufferCache object. * Also extract the encoded data and create blobs on every timeslice passed from start function or RequestData function called by UA. */ - -function MediaRecorderWrapper(mediaStream) { +function MediaStreamRecorder(mediaStream) { // if user chosen only audio option; and he tried to pass MediaStream with // both audio and video tracks; // using a dirty workaround to generate audio-only stream so that we can get audio/ogg output. - if (this.type == 'audio' && mediaStream.getVideoTracks && mediaStream.getVideoTracks().length && !navigator.mozGetUserMedia) { + if (this.type === 'audio' && mediaStream.getVideoTracks && mediaStream.getVideoTracks().length && !navigator.mozGetUserMedia) { var context = new AudioContext(); var mediaStreamSource = context.createMediaStreamSource(mediaStream); @@ -162,15 +36,21 @@ function MediaRecorderWrapper(mediaStream) { mTimeSlice = mTimeSlice || 1000; isStopRecording = false; - function startRecording() { - if (isStopRecording) return; - - mediaRecorder = new MediaRecorder(mediaStream); + mediaRecorder = new MediaRecorder(mediaStream); mediaRecorder.ondataavailable = function(e) { + if (isStopRecording) { + return; + } + + if (isPaused) { + setTimeout(startRecording, 500); + return; + } + console.log('ondataavailable', e.data.type, e.data.size, e.data); - // mediaRecorder.state == 'recording' means that media recorder is associated with "session" - // mediaRecorder.state == 'stopped' means that media recorder is detached from the "session" ... in this case; "session" will also be deleted. + // mediaRecorder.state === 'recording' means that media recorder is associated with "session" + // mediaRecorder.state === 'stopped' means that media recorder is detached from the "session" ... in this case; "session" will also be deleted. if (!e.data.size) { console.warn('Recording of', e.data.type, 'failed.'); @@ -220,20 +100,7 @@ function MediaRecorderWrapper(mediaStream) { // handler. "mTimeSlice < 0" means Session object does not push encoded data to // onDataAvailable, instead, it passive wait the client side pull encoded data // by calling requestData API. - mediaRecorder.start(0); - - // Start recording. If timeSlice has been provided, mediaRecorder will - // raise a dataavailable event containing the Blob of collected data on every timeSlice milliseconds. - // If timeSlice isn't provided, UA should call the RequestData to obtain the Blob data, also set the mTimeSlice to zero. - - setTimeout(function() { - mediaRecorder.stop(); - startRecording(); - }, mTimeSlice); - } - - // dirty workaround to fix Firefox 2nd+ intervals - startRecording(); + mediaRecorder.start(mTimeSlice); }; var isStopRecording = false; @@ -246,1136 +113,45 @@ function MediaRecorderWrapper(mediaStream) { } }; - this.ondataavailable = this.onstop = function() {}; - - // Reference to itself - var self = this; - - if (!self.mimeType && !!mediaStream.getAudioTracks) { - self.mimeType = mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length ? 'video/webm' : 'audio/ogg'; - } - - // Reference to "MediaRecorderWrapper" object - var mediaRecorder; -} - -// ================= -// StereoRecorder.js - -function StereoRecorder(mediaStream) { - // void start(optional long timeSlice) - // timestamp to fire "ondataavailable" - this.start = function(timeSlice) { - timeSlice = timeSlice || 1000; - - mediaRecorder = new StereoAudioRecorder(mediaStream, this); - - mediaRecorder.record(); - - timeout = setInterval(function() { - mediaRecorder.requestData(); - }, timeSlice); - }; - - this.stop = function() { - if (mediaRecorder) { - mediaRecorder.stop(); - clearTimeout(timeout); - } - }; - - this.ondataavailable = function() {}; - - // Reference to "StereoAudioRecorder" object - var mediaRecorder; - var timeout; -} - -// ====================== -// StereoAudioRecorder.js - -// source code from: http://typedarray.org/wp-content/projects/WebAudioRecorder/script.js + var isPaused = false; -function StereoAudioRecorder(mediaStream, root) { - // variables - var leftchannel = []; - var rightchannel = []; - var scriptprocessornode; - var recording = false; - var recordingLength = 0; - var volume; - var audioInput; - var sampleRate = root.sampleRate || 44100; // range: 22050 to 96000 - var audioContext; - var context; - - var numChannels = root.audioChannels || 2; - - this.record = function() { - recording = true; - // reset the buffers for the new recording - leftchannel.length = rightchannel.length = 0; - recordingLength = 0; - }; - - this.requestData = function() { - if (recordingLength == 0) { - requestDataInvoked = false; + this.pause = function() { + if (!mediaRecorder) { return; } - requestDataInvoked = true; - // clone stuff - var internal_leftchannel = leftchannel.slice(0); - var internal_rightchannel = rightchannel.slice(0); - var internal_recordingLength = recordingLength; - - // reset the buffers for the new recording - leftchannel.length = rightchannel.length = []; - recordingLength = 0; - requestDataInvoked = false; - - // we flat the left and right channels down - var leftBuffer = mergeBuffers(internal_leftchannel, internal_recordingLength); - var rightBuffer = mergeBuffers(internal_leftchannel, internal_recordingLength); - - // we interleave both channels together - if (numChannels === 2) { - var interleaved = interleave(leftBuffer, rightBuffer); - } else { - var interleaved = leftBuffer; - } - - // we create our wav file - var buffer = new ArrayBuffer(44 + interleaved.length * 2); - var view = new DataView(buffer); + isPaused = true; - // RIFF chunk descriptor - writeUTFBytes(view, 0, 'RIFF'); - view.setUint32(4, 44 + interleaved.length * 2, true); - writeUTFBytes(view, 8, 'WAVE'); - // FMT sub-chunk - writeUTFBytes(view, 12, 'fmt '); - view.setUint32(16, 16, true); - view.setUint16(20, 1, true); - // stereo (2 channels) - view.setUint16(22, numChannels, true); - view.setUint32(24, sampleRate, true); - view.setUint32(28, sampleRate * 4, true); - view.setUint16(32, numChannels * 2, true); - view.setUint16(34, 16, true); - // data sub-chunk - writeUTFBytes(view, 36, 'data'); - view.setUint32(40, interleaved.length * 2, true); - - // write the PCM samples - var lng = interleaved.length; - var index = 44; - var volume = 1; - for (var i = 0; i < lng; i++) { - view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); - index += 2; - } - - // our final binary blob - var blob = new Blob([view], { - type: 'audio/wav' - }); - - - root.ondataavailable(blob); - }; - - this.stop = function() { - // we stop recording - recording = false; - this.requestData(); - }; - - function interleave(leftChannel, rightChannel) { - var length = leftChannel.length + rightChannel.length; - var result = new Float32Array(length); - - var inputIndex = 0; - - for (var index = 0; index < length;) { - result[index++] = leftChannel[inputIndex]; - result[index++] = rightChannel[inputIndex]; - inputIndex++; - } - return result; - } - - function mergeBuffers(channelBuffer, recordingLength) { - var result = new Float32Array(recordingLength); - var offset = 0; - var lng = channelBuffer.length; - for (var i = 0; i < lng; i++) { - var buffer = channelBuffer[i]; - result.set(buffer, offset); - offset += buffer.length; - } - return result; - } - - function writeUTFBytes(view, offset, string) { - var lng = string.length; - for (var i = 0; i < lng; i++) { - view.setUint8(offset + i, string.charCodeAt(i)); - } - } - - // creates the audio context - - // creates the audio context - var audioContext = ObjectStore.AudioContext; - - if (!ObjectStore.AudioContextConstructor) - ObjectStore.AudioContextConstructor = new audioContext(); - - var context = ObjectStore.AudioContextConstructor; - - // creates a gain node - if (!ObjectStore.VolumeGainNode) - ObjectStore.VolumeGainNode = context.createGain(); - - var volume = ObjectStore.VolumeGainNode; - - // creates an audio node from the microphone incoming stream - if (!ObjectStore.AudioInput) - ObjectStore.AudioInput = context.createMediaStreamSource(mediaStream); - - // creates an audio node from the microphone incoming stream - var audioInput = ObjectStore.AudioInput; - - // connect the stream to the gain node - audioInput.connect(volume); - - /* From the spec: This value controls how frequently the audioprocess event is - dispatched and how many sample-frames need to be processed each call. - Lower values for buffer size will result in a lower (better) latency. - Higher values will be necessary to avoid audio breakup and glitches - Legal values are 256, 512, 1024, 2048, 4096, 8192, and 16384.*/ - var bufferSize = root.bufferSize || 2048; - if (root.bufferSize == 0) bufferSize = 0; - - if (context.createJavaScriptNode) { - scriptprocessornode = context.createJavaScriptNode(bufferSize, numChannels, numChannels); - } else if (context.createScriptProcessor) { - scriptprocessornode = context.createScriptProcessor(bufferSize, numChannels, numChannels); - } else { - throw 'WebAudio API has no support on this browser.'; - } - - bufferSize = scriptprocessornode.bufferSize; - - console.debug('using audio buffer-size:', bufferSize); - - var requestDataInvoked = false; - - // sometimes "scriptprocessornode" disconnects from he destination-node - // and there is no exception thrown in this case. - // and obviously no further "ondataavailable" events will be emitted. - // below global-scope variable is added to debug such unexpected but "rare" cases. - window.scriptprocessornode = scriptprocessornode; - - if (numChannels == 1) { - console.debug('All right-channels are skipped.'); - } - - // http://webaudio.github.io/web-audio-api/#the-scriptprocessornode-interface - scriptprocessornode.onaudioprocess = function(e) { - if (!recording || requestDataInvoked) return; - - var left = e.inputBuffer.getChannelData(0); - leftchannel.push(new Float32Array(left)); - - if (numChannels == 2) { - var right = e.inputBuffer.getChannelData(1); - rightchannel.push(new Float32Array(right)); - } - recordingLength += bufferSize; - }; - - volume.connect(scriptprocessornode); - scriptprocessornode.connect(context.destination); -} - -// ======================= -// WhammyRecorderHelper.js - -function WhammyRecorderHelper(mediaStream, root) { - this.record = function(timeSlice) { - if (!this.width) this.width = 320; - if (!this.height) this.height = 240; - - if (this.video && this.video instanceof HTMLVideoElement) { - if (!this.width) this.width = video.videoWidth || 320; - if (!this.height) this.height = video.videoHeight || 240; - } - - if (!this.video) { - this.video = { - width: this.width, - height: this.height - }; - } - - if (!this.canvas) { - this.canvas = { - width: this.width, - height: this.height - }; + if (mediaRecorder.state === 'recording') { + mediaRecorder.pause(); } - - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - - // setting defaults - if (this.video && this.video instanceof HTMLVideoElement) { - video = this.video.cloneNode(); - } else { - video = document.createElement('video'); - video.src = URL.createObjectURL(mediaStream); - - video.width = this.video.width; - video.height = this.video.height; - } - - video.muted = true; - video.play(); - - lastTime = new Date().getTime(); - whammy = new Whammy.Video(); - - console.log('canvas resolutions', canvas.width, '*', canvas.height); - console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height); - - drawFrames(); - }; - - this.clearOldRecordedFrames = function() { - frames = []; }; - var requestDataInvoked = false; - this.requestData = function() { - if (!frames.length) { - requestDataInvoked = false; + this.resume = function() { + if (!mediaRecorder) { return; } - requestDataInvoked = true; - // clone stuff - var internal_frames = frames.slice(0); - - // reset the frames for the new recording - frames = []; - - whammy.frames = dropBlackFrames(internal_frames, -1); + isPaused = false; - var WebM_Blob = whammy.compile(); - root.ondataavailable(WebM_Blob); - - requestDataInvoked = false; - }; - - var frames = []; - - var isOnStartedDrawingNonBlankFramesInvoked = false; - - function drawFrames() { - if (isStopDrawing) return; - - if (requestDataInvoked) return setTimeout(drawFrames, 100); - - var duration = new Date().getTime() - lastTime; - if (!duration) return drawFrames(); - - // via webrtc-experiment#206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); - - context.drawImage(video, 0, 0, canvas.width, canvas.height); - !isStopDrawing && frames.push({ - duration: duration, - image: canvas.toDataURL('image/webp') - }); - - if (!isOnStartedDrawingNonBlankFramesInvoked && !isBlankFrame(frames[frames.length - 1])) { - isOnStartedDrawingNonBlankFramesInvoked = true; - root.onStartedDrawingNonBlankFrames(); + if (mediaRecorder.state === 'paused') { + mediaRecorder.resume(); } - - setTimeout(drawFrames, 10); - } - - var isStopDrawing = false; - - this.stop = function() { - isStopDrawing = true; - this.requestData(); }; - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - var video; - var lastTime; - var whammy; + this.ondataavailable = this.onstop = function() {}; + // Reference to itself var self = this; - function isBlankFrame(frame, _pixTolerance, _frameTolerance) { - var localCanvas = document.createElement('canvas'); - localCanvas.width = canvas.width; - localCanvas.height = canvas.height; - var context2d = localCanvas.getContext('2d'); - - var sampleColor = { - r: 0, - g: 0, - b: 0 - }; - var maxColorDifference = Math.sqrt( - Math.pow(255, 2) + - Math.pow(255, 2) + - Math.pow(255, 2) - ); - var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; - var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; - - var matchPixCount, endPixCheck, maxPixCount; - - var image = new Image(); - image.src = frame.image; - context2d.drawImage(image, 0, 0, canvas.width, canvas.height); - var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); - matchPixCount = 0; - endPixCheck = imageData.data.length; - maxPixCount = imageData.data.length / 4; - - for (var pix = 0; pix < endPixCheck; pix += 4) { - var currentColor = { - r: imageData.data[pix], - g: imageData.data[pix + 1], - b: imageData.data[pix + 2] - }; - var colorDifference = Math.sqrt( - Math.pow(currentColor.r - sampleColor.r, 2) + - Math.pow(currentColor.g - sampleColor.g, 2) + - Math.pow(currentColor.b - sampleColor.b, 2) - ); - // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) - if (colorDifference <= maxColorDifference * pixTolerance) { - matchPixCount++; - } - } - - if (maxPixCount - matchPixCount <= maxPixCount * frameTolerance) { - return false; - } else { - return true; - } - } - - function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance) { - var localCanvas = document.createElement('canvas'); - localCanvas.width = canvas.width; - localCanvas.height = canvas.height; - var context2d = localCanvas.getContext('2d'); - var resultFrames = []; - - var checkUntilNotBlack = _framesToCheck === -1; - var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ? - _framesToCheck : _frames.length; - var sampleColor = { - r: 0, - g: 0, - b: 0 - }; - var maxColorDifference = Math.sqrt( - Math.pow(255, 2) + - Math.pow(255, 2) + - Math.pow(255, 2) - ); - var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0; - var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0; - var doNotCheckNext = false; - - for (var f = 0; f < endCheckFrame; f++) { - var matchPixCount, endPixCheck, maxPixCount; - - if (!doNotCheckNext) { - var image = new Image(); - image.src = _frames[f].image; - context2d.drawImage(image, 0, 0, canvas.width, canvas.height); - var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height); - matchPixCount = 0; - endPixCheck = imageData.data.length; - maxPixCount = imageData.data.length / 4; - - for (var pix = 0; pix < endPixCheck; pix += 4) { - var currentColor = { - r: imageData.data[pix], - g: imageData.data[pix + 1], - b: imageData.data[pix + 2] - }; - var colorDifference = Math.sqrt( - Math.pow(currentColor.r - sampleColor.r, 2) + - Math.pow(currentColor.g - sampleColor.g, 2) + - Math.pow(currentColor.b - sampleColor.b, 2) - ); - // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2) - if (colorDifference <= maxColorDifference * pixTolerance) { - matchPixCount++; - } - } - } - - if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance) { - // console.log('removed black frame : ' + f + ' ; frame duration ' + _frames[f].duration); - } else { - // console.log('frame is passed : ' + f); - if (checkUntilNotBlack) { - doNotCheckNext = true; - } - resultFrames.push(_frames[f]); - } - } - - resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)); - - if (resultFrames.length <= 0) { - // at least one last frame should be available for next manipulation - // if total duration of all frames will be < 1000 than ffmpeg doesn't work well... - resultFrames.push(_frames[_frames.length - 1]); - } - - return resultFrames; + if (!self.mimeType && !!mediaStream.getAudioTracks) { + self.mimeType = mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length ? 'video/webm' : 'audio/ogg'; } -} - -// ================= -// WhammyRecorder.js - -function WhammyRecorder(mediaStream) { - // void start(optional long timeSlice) - // timestamp to fire "ondataavailable" - this.start = function(timeSlice) { - timeSlice = timeSlice || 1000; - - mediaRecorder = new WhammyRecorderHelper(mediaStream, this); - - for (var prop in this) { - if (typeof this[prop] !== 'function') { - mediaRecorder[prop] = this[prop]; - } - } - - mediaRecorder.record(); - - timeout = setInterval(function() { - mediaRecorder.requestData(); - }, timeSlice); - }; - - this.stop = function() { - if (mediaRecorder) { - mediaRecorder.stop(); - clearTimeout(timeout); - } - }; - - this.clearOldRecordedFrames = function() { - if (mediaRecorder) { - mediaRecorder.clearOldRecordedFrames(); - } - }; - - this.ondataavailable = function() {}; - // Reference to "WhammyRecorder" object + // Reference to "MediaRecorderWrapper" object var mediaRecorder; - var timeout; -} - - -// Muaz Khan - https://github.com/muaz-khan -// neizerth - https://github.com/neizerth -// MIT License - https://www.webrtc-experiment.com/licence/ -// Documentation - https://github.com/streamproc/MediaStreamRecorder - -// Note: -// ========================================================== -// whammy.js is an "external library" -// and has its own copyrights. Taken from "Whammy" project. - - -// https://github.com/antimatter15/whammy/blob/master/LICENSE -// ========= -// Whammy.js - -// todo: Firefox now supports webp for webm containers! -// their MediaRecorder implementation works well! -// should we provide an option to record via Whammy.js or MediaRecorder API is a better solution? - -var Whammy = (function() { - - function toWebM(frames) { - var info = checkFrames(frames); - - var CLUSTER_MAX_DURATION = 30000; - - var EBML = [{ - "id": 0x1a45dfa3, // EBML - "data": [{ - "data": 1, - "id": 0x4286 // EBMLVersion - }, { - "data": 1, - "id": 0x42f7 // EBMLReadVersion - }, { - "data": 4, - "id": 0x42f2 // EBMLMaxIDLength - }, { - "data": 8, - "id": 0x42f3 // EBMLMaxSizeLength - }, { - "data": "webm", - "id": 0x4282 // DocType - }, { - "data": 2, - "id": 0x4287 // DocTypeVersion - }, { - "data": 2, - "id": 0x4285 // DocTypeReadVersion - }] - }, { - "id": 0x18538067, // Segment - "data": [{ - "id": 0x1549a966, // Info - "data": [{ - "data": 1e6, //do things in millisecs (num of nanosecs for duration scale) - "id": 0x2ad7b1 // TimecodeScale - }, { - "data": "whammy", - "id": 0x4d80 // MuxingApp - }, { - "data": "whammy", - "id": 0x5741 // WritingApp - }, { - "data": doubleToString(info.duration), - "id": 0x4489 // Duration - }] - }, { - "id": 0x1654ae6b, // Tracks - "data": [{ - "id": 0xae, // TrackEntry - "data": [{ - "data": 1, - "id": 0xd7 // TrackNumber - }, { - "data": 1, - "id": 0x63c5 // TrackUID - }, { - "data": 0, - "id": 0x9c // FlagLacing - }, { - "data": "und", - "id": 0x22b59c // Language - }, { - "data": "V_VP8", - "id": 0x86 // CodecID - }, { - "data": "VP8", - "id": 0x258688 // CodecName - }, { - "data": 1, - "id": 0x83 // TrackType - }, { - "id": 0xe0, // Video - "data": [{ - "data": info.width, - "id": 0xb0 // PixelWidth - }, { - "data": info.height, - "id": 0xba // PixelHeight - }] - }] - }] - }] - }]; - - //Generate clusters (max duration) - var frameNumber = 0; - var clusterTimecode = 0; - while (frameNumber < frames.length) { - - var clusterFrames = []; - var clusterDuration = 0; - do { - clusterFrames.push(frames[frameNumber]); - clusterDuration += frames[frameNumber].duration; - frameNumber++; - } while (frameNumber < frames.length && clusterDuration < CLUSTER_MAX_DURATION); - - var clusterCounter = 0; - var cluster = { - "id": 0x1f43b675, // Cluster - "data": [{ - "data": clusterTimecode, - "id": 0xe7 // Timecode - }].concat(clusterFrames.map(function(webp) { - var block = makeSimpleBlock({ - discardable: 0, - frame: webp.data.slice(4), - invisible: 0, - keyframe: 1, - lacing: 0, - trackNum: 1, - timecode: Math.round(clusterCounter) - }); - clusterCounter += webp.duration; - return { - data: block, - id: 0xa3 - }; - })) - }; //Add cluster to segment - EBML[1].data.push(cluster); - clusterTimecode += clusterDuration; - } - - return generateEBML(EBML); - } - - // sums the lengths of all the frames and gets the duration - - function checkFrames(frames) { - if (!frames[0]) { - console.warn('Something went wrong. Maybe WebP format is not supported in the current browser.'); - return; - } - - var width = frames[0].width, - height = frames[0].height, - duration = frames[0].duration; - - for (var i = 1; i < frames.length; i++) { - duration += frames[i].duration; - } - return { - duration: duration, - width: width, - height: height - }; - } - - function numToBuffer(num) { - var parts = []; - while (num > 0) { - parts.push(num & 0xff); - num = num >> 8; - } - return new Uint8Array(parts.reverse()); - } - - function strToBuffer(str) { - return new Uint8Array(str.split('').map(function(e) { - return e.charCodeAt(0); - })); - } - - function bitsToBuffer(bits) { - var data = []; - var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; - bits = pad + bits; - for (var i = 0; i < bits.length; i += 8) { - data.push(parseInt(bits.substr(i, 8), 2)); - } - return new Uint8Array(data); - } - - function generateEBML(json) { - var ebml = []; - for (var i = 0; i < json.length; i++) { - var data = json[i].data; - if (typeof data == 'object') data = generateEBML(data); - if (typeof data == 'number') data = bitsToBuffer(data.toString(2)); - if (typeof data == 'string') data = strToBuffer(data); - - var len = data.size || data.byteLength || data.length; - var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); - var size_str = len.toString(2); - var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str; - var size = (new Array(zeroes)).join('0') + '1' + padded; - - ebml.push(numToBuffer(json[i].id)); - ebml.push(bitsToBuffer(size)); - ebml.push(data); - } - - return new Blob(ebml, { - type: "video/webm" - }); - } - - function toBinStr_old(bits) { - var data = ''; - var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : ''; - bits = pad + bits; - for (var i = 0; i < bits.length; i += 8) { - data += String.fromCharCode(parseInt(bits.substr(i, 8), 2)); - } - return data; - } - - function generateEBML_old(json) { - var ebml = ''; - for (var i = 0; i < json.length; i++) { - var data = json[i].data; - if (typeof data == 'object') data = generateEBML_old(data); - if (typeof data == 'number') data = toBinStr_old(data.toString(2)); - - var len = data.length; - var zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8); - var size_str = len.toString(2); - var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str; - var size = (new Array(zeroes)).join('0') + '1' + padded; - - ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data; - - } - return ebml; - } - - function makeSimpleBlock(data) { - var flags = 0; - if (data.keyframe) flags |= 128; - if (data.invisible) flags |= 8; - if (data.lacing) flags |= (data.lacing << 1); - if (data.discardable) flags |= 1; - if (data.trackNum > 127) { - throw "TrackNumber > 127 not supported"; - } - var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e) { - return String.fromCharCode(e); - }).join('') + data.frame; - - return out; - } - - function parseWebP(riff) { - var VP8 = riff.RIFF[0].WEBP[0]; - - var frame_start = VP8.indexOf('\x9d\x01\x2a'); // A VP8 keyframe starts with the 0x9d012a header - for (var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i); - - var width, height, tmp; - - //the code below is literally copied verbatim from the bitstream spec - tmp = (c[1] << 8) | c[0]; - width = tmp & 0x3FFF; - tmp = (c[3] << 8) | c[2]; - height = tmp & 0x3FFF; - return { - width: width, - height: height, - data: VP8, - riff: riff - }; - } - - function parseRIFF(string) { - var offset = 0; - var chunks = {}; - - while (offset < string.length) { - var id = string.substr(offset, 4); - var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i) { - var unpadded = i.charCodeAt(0).toString(2); - return (new Array(8 - unpadded.length + 1)).join('0') + unpadded; - }).join(''), 2); - var data = string.substr(offset + 4 + 4, len); - offset += 4 + 4 + len; - chunks[id] = chunks[id] || []; - - if (id == 'RIFF' || id == 'LIST') { - chunks[id].push(parseRIFF(data)); - } else { - chunks[id].push(data); - } - } - return chunks; - } - - function doubleToString(num) { - return [].slice.call( - new Uint8Array((new Float64Array([num])).buffer), 0).map(function(e) { - return String.fromCharCode(e); - }).reverse().join(''); - } - - // a more abstract-ish API - - function WhammyVideo(duration) { - this.frames = []; - this.duration = duration || 1; - this.quality = 100; - } - - WhammyVideo.prototype.add = function(frame, duration) { - if ('canvas' in frame) { //CanvasRenderingContext2D - frame = frame.canvas; - } - - if ('toDataURL' in frame) { - frame = frame.toDataURL('image/webp', this.quality); - } - - if (!(/^data:image\/webp;base64,/ig).test(frame)) { - throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp"; - } - this.frames.push({ - image: frame, - duration: duration || this.duration - }); - }; - WhammyVideo.prototype.compile = function() { - return new toWebM(this.frames.map(function(frame) { - var webp = parseWebP(parseRIFF(atob(frame.image.slice(23)))); - webp.duration = frame.duration; - return webp; - })); - }; - return { - Video: WhammyVideo, - toWebM: toWebM - }; -})(); - -// Muaz Khan - https://github.com/muaz-khan -// neizerth - https://github.com/neizerth -// MIT License - https://www.webrtc-experiment.com/licence/ -// Documentation - https://github.com/streamproc/MediaStreamRecorder -// ========================================================== -// GifRecorder.js - -function GifRecorder(mediaStream) { - if (!window.GIFEncoder) { - throw 'Please link: https://cdn.webrtc-experiment.com/gif-recorder.js'; - } - - // void start(optional long timeSlice) - // timestamp to fire "ondataavailable" - this.start = function(timeSlice) { - timeSlice = timeSlice || 1000; - - var imageWidth = this.videoWidth || 320; - var imageHeight = this.videoHeight || 240; - - canvas.width = video.width = imageWidth; - canvas.height = video.height = imageHeight; - - // external library to record as GIF images - gifEncoder = new GIFEncoder(); - - // void setRepeat(int iter) - // Sets the number of times the set of GIF frames should be played. - // Default is 1; 0 means play indefinitely. - gifEncoder.setRepeat(0); - - // void setFrameRate(Number fps) - // Sets frame rate in frames per second. - // Equivalent to setDelay(1000/fps). - // Using "setDelay" instead of "setFrameRate" - gifEncoder.setDelay(this.frameRate || 200); - - // void setQuality(int quality) - // Sets quality of color quantization (conversion of images to the - // maximum 256 colors allowed by the GIF specification). - // Lower values (minimum = 1) produce better colors, - // but slow processing significantly. 10 is the default, - // and produces good color mapping at reasonable speeds. - // Values greater than 20 do not yield significant improvements in speed. - gifEncoder.setQuality(this.quality || 1); - - // Boolean start() - // This writes the GIF Header and returns false if it fails. - gifEncoder.start(); - - startTime = Date.now(); - - function drawVideoFrame(time) { - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - if (typeof lastFrameTime === undefined) { - lastFrameTime = time; - } - - // ~10 fps - if (time - lastFrameTime < 90) return; - - context.drawImage(video, 0, 0, imageWidth, imageHeight); - - gifEncoder.addFrame(context); - - // console.log('Recording...' + Math.round((Date.now() - startTime) / 1000) + 's'); - // console.log("fps: ", 1000 / (time - lastFrameTime)); - - lastFrameTime = time; - } - - lastAnimationFrame = requestAnimationFrame(drawVideoFrame); - - timeout = setTimeout(doneRecording, timeSlice); - }; - - function doneRecording() { - endTime = Date.now(); - - var gifBlob = new Blob([new Uint8Array(gifEncoder.stream().bin)], { - type: 'image/gif' - }); - self.ondataavailable(gifBlob); - - // todo: find a way to clear old recorded blobs - gifEncoder.stream().bin = []; - }; - - this.stop = function() { - if (lastAnimationFrame) { - cancelAnimationFrame(lastAnimationFrame); - clearTimeout(timeout); - doneRecording(); - } - }; - - this.ondataavailable = function() {}; - this.onstop = function() {}; - - // Reference to itself - var self = this; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - - var video = document.createElement('video'); - video.muted = true; - video.autoplay = true; - video.src = URL.createObjectURL(mediaStream); - video.play(); - - var lastAnimationFrame = null; - var startTime, endTime, lastFrameTime; - - var gifEncoder; - var timeout; } -// ______________________ -// MultiStreamRecorder.js - -function MultiStreamRecorder(mediaStream) { - if (!mediaStream) throw 'MediaStream is mandatory.'; - - var self = this; - var isFirefox = !!navigator.mozGetUserMedia; - - this.stream = mediaStream; - - // void start(optional long timeSlice) - // timestamp to fire "ondataavailable" - this.start = function(timeSlice) { - audioRecorder = new MediaStreamRecorder(mediaStream); - videoRecorder = new MediaStreamRecorder(mediaStream); - - audioRecorder.mimeType = 'audio/ogg'; - videoRecorder.mimeType = 'video/webm'; - - for (var prop in this) { - if (typeof this[prop] !== 'function') { - audioRecorder[prop] = videoRecorder[prop] = this[prop]; - } - } - - audioRecorder.ondataavailable = function(blob) { - if (!audioVideoBlobs[recordingInterval]) { - audioVideoBlobs[recordingInterval] = {}; - } - - audioVideoBlobs[recordingInterval].audio = blob; - - if (audioVideoBlobs[recordingInterval].video && !audioVideoBlobs[recordingInterval].onDataAvailableEventFired) { - audioVideoBlobs[recordingInterval].onDataAvailableEventFired = true; - fireOnDataAvailableEvent(audioVideoBlobs[recordingInterval]); - } - }; - - videoRecorder.ondataavailable = function(blob) { - if (isFirefox) { - return self.ondataavailable({ - video: blob, - audio: blob - }); - } - - if (!audioVideoBlobs[recordingInterval]) { - audioVideoBlobs[recordingInterval] = {}; - } - - audioVideoBlobs[recordingInterval].video = blob; - - if (audioVideoBlobs[recordingInterval].audio && !audioVideoBlobs[recordingInterval].onDataAvailableEventFired) { - audioVideoBlobs[recordingInterval].onDataAvailableEventFired = true; - fireOnDataAvailableEvent(audioVideoBlobs[recordingInterval]); - } - }; - - function fireOnDataAvailableEvent(blobs) { - recordingInterval++; - self.ondataavailable(blobs); - - ConcatenateBlobs([blobs.audio, blobs.video], 'audio/wav', function(concatenated) { - console.debug('Recorded blob size:', bytesToSize(concatenated.size)); - }); - } - - videoRecorder.onstop = audioRecorder.onstop = function(error) { - self.onstop(error); - }; - - if (!isFirefox) { - // to make sure both audio/video are synced. - videoRecorder.onStartedDrawingNonBlankFrames = function() { - videoRecorder.clearOldRecordedFrames(); - audioRecorder.start(timeSlice); - }; - videoRecorder.start(timeSlice); - } else { - videoRecorder.start(timeSlice); - } - }; - - this.stop = function() { - if (audioRecorder) audioRecorder.stop(); - if (videoRecorder) videoRecorder.stop(); - }; - - this.ondataavailable = function(blob) { - console.log('ondataavailable..', blob); - }; - - this.onstop = function(error) { - console.warn('stopped..', error); - }; - - var audioRecorder; - var videoRecorder; - - var audioVideoBlobs = {}; - var recordingInterval = 0; -} function bytesToSize(bytes) { var k = 1000; @@ -1386,66 +162,3 @@ function bytesToSize(bytes) { var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; } - -// Last time updated at Nov 18, 2014, 08:32:23 - -// Latest file can be found here: https://cdn.webrtc-experiment.com/ConcatenateBlobs.js - -// Muaz Khan - www.MuazKhan.com -// MIT License - www.WebRTC-Experiment.com/licence -// Source Code - https://github.com/muaz-khan/ConcatenateBlobs -// Demo - https://www.WebRTC-Experiment.com/ConcatenateBlobs/ - -// ___________________ -// ConcatenateBlobs.js - -// Simply pass array of blobs. -// This javascript library will concatenate all blobs in single "Blob" object. - -(function() { - window.ConcatenateBlobs = function(blobs, type, callback) { - var buffers = []; - - var index = 0; - - function readAsArrayBuffer() { - if (!blobs[index]) { - return concatenateBuffers(); - } - var reader = new FileReader(); - reader.onload = function(event) { - buffers.push(event.target.result); - index++; - readAsArrayBuffer(); - }; - reader.readAsArrayBuffer(blobs[index]); - } - - readAsArrayBuffer(); - - function concatenateBuffers() { - var byteLength = 0; - buffers.forEach(function(buffer) { - byteLength += buffer.byteLength; - }); - - var tmp = new Uint16Array(byteLength); - var lastOffset = 0; - buffers.forEach(function(buffer) { - // BYTES_PER_ELEMENT == 2 for Uint16Array - var reusableByteLength = buffer.byteLength; - if (reusableByteLength % 2 != 0) { - buffer = buffer.slice(0, reusableByteLength - 1) - } - tmp.set(new Uint16Array(buffer), lastOffset); - lastOffset += reusableByteLength; - }); - - var blob = new Blob([tmp.buffer], { - type: type - }); - - callback(blob); - } - }; -})(); diff --git a/Record-Entire-Meeting/Merge-Recordings.js b/Record-Entire-Meeting/Merge-Recordings.js deleted file mode 100755 index 36585144..00000000 --- a/Record-Entire-Meeting/Merge-Recordings.js +++ /dev/null @@ -1,96 +0,0 @@ -var exec = require('child_process').exec; -var fs = require('fs'); -var isWindows = !!process.platform.match(/^win/); -var socket; - -module.exports = exports = function(files, _socket) { - socket = _socket; - - if(isWindows) { - mergeForWindows(files); - return; - } - mergeForLinuxOrMac(files); -}; - -// linux to merge WAV/WebM info single WebM -function mergeForLinuxOrMac(files, times) { - // its probably *nix, assume ffmpeg is available - - // room-directory - var dir = __dirname + '/uploads/' + files.roomId + '/'; - - var audioFile = dir + files.audio.name; // audio file - var videoFile = dir + files.video.name; // video file - - // resulting single WebM file - var mergedFile = dir + files.audio.name.split('.')[0] + '-merged.webm'; - - var util = require('util'), - exec = require('child_process').exec; - - // ffmpeg command used in linux to merge WAV/WebM into WebM - var command = "ffmpeg -i " + audioFile + " -i " + videoFile + " -map 0:0 -map 1:0 " + mergedFile; - - // invoke ffmpeg via BASH - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - // if failed to merge then try-again-and-again until 30 minutes - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - mergeForLinuxOrMac(files, times); - }, 1000); - } else { - // old audio file isn't needed - fs.unlink(audioFile); - - // old video file isn't needed - fs.unlink(videoFile); - - console.log('dev-logs', 'Successfully merged WAV/WebM files from recording interval ' + files.interval + '.'); - } - }); -} - -// for windows -function mergeForWindows(files, times) { - // directory address - var dir = __dirname + '\\uploads\\' + files.roomId + '\\'; - - // bash file - var merger = __dirname + '\\bat-files\\merger.bat'; - - var audioFile = dir + files.audio.name; // audio file - var videoFile = dir + files.video.name; // video file - - // resulting single WebM file - var mergedFile = dir + files.audio.name.split('.')[0] + '-merged.webm'; - - // ffmpeg command used to merge WAV/WebM into single WebM - var command = merger + ', ' + audioFile + " " + videoFile + " " + mergedFile + ''; - - // invoke *.bat file via BASH - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - // if failed to merge then try-again-and-again until 30 minutes - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - mergeForWindows(files, times); - }, 1000); - } else { - // old audio file isn't needed - fs.unlink(audioFile); - - // old video file isn't needed - fs.unlink(videoFile); - - console.log('dev-logs', 'Successfully merged WAV/WebM files from recording interval ' + files.interval + '.'); - } - }); -} diff --git a/Record-Entire-Meeting/Nodejs-Recording-Handler.js b/Record-Entire-Meeting/Nodejs-Recording-Handler.js index 124e125f..b4de90c9 100755 --- a/Record-Entire-Meeting/Nodejs-Recording-Handler.js +++ b/Record-Entire-Meeting/Nodejs-Recording-Handler.js @@ -1,133 +1,73 @@ -module.exports = exports = function(app, successCallback) { - var io = require('socket.io')(app, { - log: false, - origins: '*:*' - }); - - io.on('connection', SocketExtender); +// Muaz Khan - www.MuazKhan.com +// MIT License - www.webrtc-experiment.com/licence +// Documentation - github.com/streamproc/MediaStreamRecorder +module.exports = exports = function(socket) { + SocketExtender(socket); }; - -// this lib is used to create directories var mkdirp = require('mkdirp'); var fs = require('fs'); -var isWindows = !!process.platform.match(/^win/); var WriteToDisk = require('./Write-Recordings-To-Disk.js'); var ConcatenateRecordings = require('./Concatenate-Recordings.js'); -// each room is having a unique directory -// this object stores those info -// e.g. -// roomsDirs[roomid] = { -// usersIndexed: { -// interval: currentInterval, // that are uploaded to server -// fileName: hisUniqueFileName // it is "userid" though -// } -// }; var roomsDirs = {}; function SocketExtender(socket) { - // reading io.connect('/?query=parameter&other=value'); var params = socket.handshake.query; - // used to invoke ffmpeg via bash in windows - - // all recorded blobs are passed here - // it is a function because we need to make sure - // 1) directory is either empty or NOT exists - // 2) if directory exists, then delete all nested files & directory itself - // 3) use loop-back until directory is empty of the existing files function onGettingRecordedMessages(data, callback) { - // blobs that are uploaded from client - // 1) audio-blob - // 2) video-blob - var files = JSON.parse(data); - - // this user is having unique roomid - // it is used to understand "room-directory" - socket.roomId = files.roomId; - - // this user is having unique userid - // it is used to understand "file-name" - socket.userId = files.userId; - - // if global-variable isn't having "roomid" - // todo: merely this code is checking to see if directory exists or NOT. - // try to check directory-presence outside of this block as well. - if (!roomsDirs[files.roomId]) { - // default values for room - roomsDirs[files.roomId] = { + var file = JSON.parse(data); + + socket.roomId = file.roomId; + socket.userId = file.userId; + + if (!roomsDirs[file.roomId]) { + roomsDirs[file.roomId] = { usersIndexed: {} }; - console.log('dev-logs', 'roomsDirs: ' + files.roomId); - - // if directory doesn't exists, then create it - // todo: follow this pattern - // 1) check to see if directory exists, delete it first - // 2) remove all nested files from existing directory - // 3) create new directory - // 4) make sure that this stuff is handled above of this block (so that it works for all the codes) - if (!fs.existsSync('./uploads/' + files.roomId)) { - createNewDir('./uploads/' + files.roomId, data, onGettingRecordedMessages, callback); + if (!fs.existsSync('./uploads/' + file.roomId)) { + createNewDir('./uploads/' + file.roomId, data, onGettingRecordedMessages, callback); return; } - // loop-back: this time "if-block" will be skipped because "roomsDirs" is now having "roomid" onGettingRecordedMessages(data, callback); return; } - // if user doesn't exists in the global-variable - // todo: if user exists and interval is "1" then what?? - // suggestion: delete all user's existing files - if (!roomsDirs[files.roomId].usersIndexed[files.userId]) { - console.log('dev-logs', 'roomsDirs - setting userid: ' + files.userId); - - // user's defaults - roomsDirs[files.roomId].usersIndexed[files.userId] = { - interval: files.interval, - fileName: files.fileName + if (!roomsDirs[file.roomId].usersIndexed[file.userId]) { + roomsDirs[file.roomId].usersIndexed[file.userId] = { + interval: file.interval, + fileName: file.fileName }; } - // interval is set on each upload - roomsDirs[files.roomId].usersIndexed[files.userId].interval = files.interval; + roomsDirs[file.roomId].usersIndexed[file.userId].interval = file.interval; + + console.log('writing file do disk', file.interval); - // write files to disk - WriteToDisk(files, socket); + WriteToDisk(file, socket); - // let client know that his blobs are successfully uploaded callback(); } - // this event is used to get uploaded blobs from client socket.on('recording-message', onGettingRecordedMessages); - - // all these three events are used to detect if "recording-is-ended" - // todo: setTimeout should be added in "disconnect" event - // and all other events should be removed??? - // socket.on('disconnect', onRecordingStopped); socket.on('stream-stopped', onRecordingStopped); + socket.on('disconnect', onRecordingStopped); function onRecordingStopped() { - // 1) if directory doesn't exists - // 2) if user doesn't exists - // then, simply skip the rest + if (!socket.roomId || !socket.userId) return; + + console.log('onRecordingStopped'); + if (!roomsDirs[socket.roomId] || !roomsDirs[socket.roomId].usersIndexed[socket.userId]) { + console.log('skipped', socket.roomId, socket.userId); return; } - // get the user (file-name, interval) var user = roomsDirs[socket.roomId].usersIndexed[socket.userId]; - - /* - files.lastIndex = files.interval + 1; - ConcatenateRecordings(files, socket); - */ - ConcatenateRecordings({ fileName: user.fileName, lastIndex: user.interval + 1, @@ -136,14 +76,10 @@ function SocketExtender(socket) { interval: user.interval }, socket); - // delete users index so that he can record again if (!!roomsDirs[socket.roomId] && !!roomsDirs[socket.roomId].usersIndexed[socket.userId]) { delete roomsDirs[socket.roomId].usersIndexed[socket.userId]; } - // if he is the alone user in this room - // delete room from global-variable - // todo???? delete room-directory as well??? if (!!roomsDirs[socket.roomId] && Object.keys(roomsDirs[socket.roomId].usersIndexed).length <= 1) { delete roomsDirs[socket.roomId]; } @@ -159,4 +95,4 @@ function createNewDir(path, data, onGettingRecordedMessages, callback) { } onGettingRecordedMessages(data, callback); }); -} \ No newline at end of file +} diff --git a/Record-Entire-Meeting/README.md b/Record-Entire-Meeting/README.md index 57d132e2..5107ff8a 100644 --- a/Record-Entire-Meeting/README.md +++ b/Record-Entire-Meeting/README.md @@ -14,6 +14,11 @@ This application runs top over `MediaStreamRecorder.js`: * https://github.com/streamproc/MediaStreamRecorder +# Browser Support + +1. Canary with `chrome://flags/#enable-experimental-web-platform-features` +2. Firefox + # Goals * Record both audio/video from each user participating in a meeting room. @@ -21,31 +26,22 @@ This application runs top over `MediaStreamRecorder.js`: * Merge/Mux then Concatenate using Ffmpeg on Node.js server * Scale videos at the end into a single grid-like stream so that later viewers are given single file containing all the videos and audios. -# Helper Scripts - -[Browser-Recording-Helper.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/Browser-Recording-Helper.js): - -> This script provides browser public API. - -[Write-Recordings-To-Disk.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/Write-Recordings-To-Disk.js): - -> This script helps writing both audio/video files to nodejs disk. +# Use in your own applications -[Merge-Recordings.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/Merge-Recordings.js): +```javascript +// 1st step +var NodeJsRecordingHandler = require('./Nodejs-Recording-Handler.js'); -> This script helps merging/muxing both WAV/WebM into single WebM. +io.on('connection', function(socket) { + // 2nd & last step: + // call below line for each socket connection + // it will never affect your other socket.io events or objects + NodeJsRecordingHandler(socket); -[Concatenate-Recordings.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/Concatenate-Recordings.js) - -> This script helps concatenating all interval based recordings into single WebM file. It runs in node.js server. - -[Scale-Recordings.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/Scale-Recordings.js): - -> This script is currently optional. It helps increasing audio/video quality. - -[MediaStreamRecorder.js](https://github.com/streamproc/Record-Entire-Meeting/blob/master/MediaStreamRecorder.js): + // your custom socket.io code goes here +}); +``` -> It is a javascript library support cross-browser intervals-based audio/video/gif recordings. ## License diff --git a/Record-Entire-Meeting/Scale-Recordings.js b/Record-Entire-Meeting/Scale-Recordings.js deleted file mode 100755 index 8e9e5af9..00000000 --- a/Record-Entire-Meeting/Scale-Recordings.js +++ /dev/null @@ -1,65 +0,0 @@ -var exec = require('child_process').exec; -var fs = require('fs'); -var isWindows = !!process.platform.match(/^win/); - -module.exports = exports = function(files) { - if(isWindows) { - scaleInWindows(files); - return; - } - scaleInLinuxOrMac(files); -}; - -function scaleInLinuxOrMac(files, times, fileNotExistsTries) { - console.log('dev-logs', 'Scaling recording interval ' + files.interval + '.'); - - var uploadsFolder = __dirname + '/uploads/' + files.roomId + '/'; - - var input_file = uploadsFolder + files.userId + '.webm'; - var output_file = uploadsFolder + files.userId + '-scaled.webm'; - var command = 'ffmpeg -y -i ' + input_file + ' -vf scale=640x360,setdar=16:9:max=1000 ' + output_file; - - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - scaleInLinuxOrMac(files, times); - }, 1000); - } else { - console.log('dev-logs', 'Successfully scaled from recording interval ' + files.interval + '.'); - fs.unlink(input_file); - } - }); -} - -function scaleInWindows(files, times, fileNotExistsTries) { - console.log('dev-logs', 'Scaling from recording interval ' + files.interval + '.'); - - var uploadsFolder = __dirname + '\\uploads\\' + files.roomId + '\\'; - - - var concatenate_bat = __dirname + '\\bat-files\\scale.bat'; - - var input_file = uploadsFolder + files.userId + '.webm'; - var output_file = uploadsFolder + files.userId + '-scaled.webm'; - var command = concatenate_bat + ', ' + input_file + ' ' + output_file; - - exec(command, function(error, stdout, stderr) { - if (error) { - console.log('ffmpeg-error', 'ffmpeg : An error occurred: ' + error.stack); - - times < 30 * 60 && setTimeout(function() { - if (!times) times = 0; - times += 1; - scaleInWindows(files, times); - }, 1000); - } else { - console.log('dev-logs', 'Successfully scaled from recording interval ' + files.interval + '.'); - - fs.unlink(input_file); - } - }); -} diff --git a/Record-Entire-Meeting/Write-Recordings-To-Disk.js b/Record-Entire-Meeting/Write-Recordings-To-Disk.js index 55153bda..3851e357 100755 --- a/Record-Entire-Meeting/Write-Recordings-To-Disk.js +++ b/Record-Entire-Meeting/Write-Recordings-To-Disk.js @@ -1,90 +1,42 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.webrtc-experiment.com/licence +// Documentation - github.com/streamproc/MediaStreamRecorder var fs = require('fs'); -var isWindows = !!process.platform.match(/^win/); -var MergeRecordings = require('./Merge-Recordings.js'); var socket; -module.exports = exports = function(files, _socket) { +module.exports = exports = function(file, _socket) { socket = _socket; - writeToDisk(files); + writeToDisk(file); }; -// this function is used to write files to disk -function writeToDisk(files) { - // a separate function is used to write audio/video files - // that's why "roomid" is included for each blob - files.audio.roomId = files.roomId; +function writeToDisk(file) { + file.data.roomId = file.roomId; + writeToDiskInternal(file.data, function() { - // todo: Firefox can use StereoAudioRecorder.js as well - // so it should be: if(files.singleBlob) {} - if (!!files.isFirefox) { - files.audio.isFirefox = !!files.isFirefox; - } else { - files.video.roomId = files.roomId; - } - - if (files.lastIndex) { - console.log('dev-logs', 'Seems ' + files.interval + ' is last interval.'); - } - - // this function takes single blob and writes to disk - writeToDiskInternal(files.audio, function() { - // if it is firefox, then quickly concatenate all files - // NO-need to merge using ffmpeg because Firefox is supporting - - // todo: Firefox can uploaded multiple blobs. - // use: if(files.singleBlob) {} - if (!!files.isFirefox) { - console.log('dev-logs', 'Seems Firefox. Thats why skipped merging from recording interval ' + files.interval + '.'); - - if (!process.platform.match(/^win/)) { - concatenateInMac(files); - } else { - concatenateInWindows(files); - } - return; - } - - // upload video file - writeToDiskInternal(files.video, function() { - // merge WAV/WebM into single WebM - // todo???? handle it in "disconnect" event instead??? - MergeRecordings(files, socket); - }); }); } -// this file is used to write single blob into disk function writeToDiskInternal(file, callback) { - var isFirefox = file.isFirefox; // todo: files.isSingleBlob - - // file info (file name, extension, directory, etc.) - var fileRootName = file.name.split('.').shift() + (isFirefox ? '-merged' : ''), - fileExtension = isFirefox ? 'webm' : file.name.split('.').pop(), + var fileRootName = file.name.split('.').shift(), + fileExtension = 'webm', filePathBase = './uploads/' + file.roomId + '/', fileRootNameWithBase = filePathBase + fileRootName, filePath = fileRootNameWithBase + '.' + fileExtension, fileID = 2, fileBuffer; - console.log('dev-logs', 'Uploading ' + fileExtension + ' file from recording interval ' + file.interval + '.'); - - // if file already exits, delete it if (fs.existsSync(filePath)) { fs.unlink(filePath); } - // reformat the DataURL file.contents = file.contents.split(',').pop(); - // convert DataURL to ArrayBuffer fileBuffer = new Buffer(file.contents, "base64"); - // write ArrayBuffer to disk fs.writeFile(filePath, fileBuffer, function(error) { - if (error) throw error; // todo?????? + if (error) throw error; - console.log('dev-logs', 'Successfully uploaded ' + fileExtension + ' file from recording interval ' + file.interval + '.'); callback(); }); } diff --git a/Record-Entire-Meeting/bat-files/concatenate.bat b/Record-Entire-Meeting/bat-files/concatenate.bat deleted file mode 100755 index 86c0f02f..00000000 --- a/Record-Entire-Meeting/bat-files/concatenate.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"C:\ffmpeg\bin\ffmpeg.exe" -y -f concat -i %1 -c copy %2 \ No newline at end of file diff --git a/Record-Entire-Meeting/bat-files/merger.bat b/Record-Entire-Meeting/bat-files/merger.bat deleted file mode 100755 index 965b5be5..00000000 --- a/Record-Entire-Meeting/bat-files/merger.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"C:\ffmpeg\bin\ffmpeg.exe" -y -itsoffset -00:00:00 -i %1 -itsoffset -00:00:00 -i %2 %3 \ No newline at end of file diff --git a/Record-Entire-Meeting/bat-files/scale.bat b/Record-Entire-Meeting/bat-files/scale.bat deleted file mode 100755 index 1c86b752..00000000 --- a/Record-Entire-Meeting/bat-files/scale.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"C:\ffmpeg\bin\ffmpeg.exe" -y -i %1 -vf scale=640x360,setdar=16:9:max=1000 %2 \ No newline at end of file diff --git a/Record-Entire-Meeting/index.html b/Record-Entire-Meeting/index.html index 4d5d6a31..42a7e997 100755 --- a/Record-Entire-Meeting/index.html +++ b/Record-Entire-Meeting/index.html @@ -1,53 +1,74 @@

https://github.com/streamproc/Record-Entire-Meeting

+ + +Recording duration: 00:00
+Ffmpeg progress: 00:00 remaining +
+ + \ No newline at end of file diff --git a/Record-Entire-Meeting/package.json b/Record-Entire-Meeting/package.json index e216da5b..1324ed0d 100755 --- a/Record-Entire-Meeting/package.json +++ b/Record-Entire-Meeting/package.json @@ -1,7 +1,7 @@ { "name": "record-entire-meeting", "preferGlobal": true, - "version": "1.0.1", + "version": "1.0.2", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", @@ -29,8 +29,9 @@ "muaz-khan" ], "dependencies": { - "socket.io": "latest", - "mkdirp": "latest" + "socket.io": "0.9.17", + "mkdirp": "0.5.1", + "fluent-ffmpeg" : "2.0.1" }, "analyze": false, "license": "MIT", diff --git a/Record-Entire-Meeting/server.js b/Record-Entire-Meeting/server.js index ce2a0911..96725693 100755 --- a/Record-Entire-Meeting/server.js +++ b/Record-Entire-Meeting/server.js @@ -1,3 +1,7 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.webrtc-experiment.com/licence +// Documentation - github.com/streamproc/MediaStreamRecorder + var isUseHTTPs = !(!!process.env.PORT || !!process.env.IP); var server = require(isUseHTTPs ? 'https' : 'http'), @@ -22,8 +26,14 @@ function serverHandler(request, response) { return; } + var contentType = {}; + if (fs.statSync(filename).isDirectory()) { filename += '/index.html'; + + contentType = { + 'Content-Type': 'text/html' + }; } @@ -37,7 +47,7 @@ function serverHandler(request, response) { return; } - response.writeHead(200); + response.writeHead(200, contentType); response.write(file, 'binary'); response.end(); }); @@ -58,7 +68,11 @@ app = app.listen(process.env.PORT || 9001, process.env.IP || "0.0.0.0", function console.log("Server listening at", addr.address + ":" + addr.port); }); -require('./Nodejs-Recording-Handler.js')(app, function(socket) { - // do extra stuff with "socket"! +var NodeJsRecordingHandler = require('./Nodejs-Recording-Handler.js'); + +var io = require('socket.io').listen(app, { + log: false, + origins: '*:*' }); +io.on('connection', NodeJsRecordingHandler); diff --git a/RecordRTC/AudioVideo-on-Firefox.html b/RecordRTC/AudioVideo-on-Firefox.html index dee30291..1c19ceb8 100644 --- a/RecordRTC/AudioVideo-on-Firefox.html +++ b/RecordRTC/AudioVideo-on-Firefox.html @@ -51,7 +51,7 @@ - + diff --git a/RecordRTC/Canvas-Recording/README.md b/RecordRTC/Canvas-Recording/README.md index c662be28..faa63516 100644 --- a/RecordRTC/Canvas-Recording/README.md +++ b/RecordRTC/Canvas-Recording/README.md @@ -1,4 +1,9 @@ -## HTML2Canvas & RecordRTC / [Demo](https://www.webrtc-experiment.com/RecordRTC/Canvas-Recording/) +## Record Entire WebPage or Canvas2D + +Demos: + +1. [Record entire DIV including video, image, textarea, input, drag/move/resize, everything](https://www.webrtc-experiment.com/RecordRTC/Canvas-Recording/) +2. [Record canvas 2D drawings, lines, shapes, texts, images, drag/resize/enlarge/move via a huge drawing tool!](https://www.webrtc-experiment.com/RecordRTC/Canvas-Recording/record-canvas-drawings.html) This [WebRTC](https://www.webrtc-experiment.com/) experiment is using [RecordRTC.js](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC) to record HTML/Canvas into webm; where [html2canvas.js](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/part-of-screen-sharing) is used to capture HTML-snapshots. Those snapshots are encoded in webp; and then encoded again in webm. @@ -11,13 +16,14 @@ You can say it: "HTML/Canvas Recording using RecordRTC"! = ```html - - + +
+ + + +``` + +Live Demo: + +* https://www.webrtc-experiment.com/RecordRTC/Canvas-Recording/record-canvas-drawings.html + +Watch a video: https://vimeo.com/152119435 + += + [RecordRTC](https://www.webrtc-experiment.com/RecordRTC/) is a server-less (entire client-side) JavaScript library can be used to record WebRTC audio/video media streams. It supports cross-browser audio/video recording. 1. [RecordRTC to Node.js](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/RecordRTC-to-Nodejs) diff --git a/RecordRTC/Canvas-Recording/WebRTC.png b/RecordRTC/Canvas-Recording/WebRTC.png new file mode 100644 index 00000000..0930e561 Binary files /dev/null and b/RecordRTC/Canvas-Recording/WebRTC.png differ diff --git a/RecordRTC/Canvas-Recording/canvas-designer.js b/RecordRTC/Canvas-Recording/canvas-designer.js new file mode 100644 index 00000000..42799f98 --- /dev/null +++ b/RecordRTC/Canvas-Recording/canvas-designer.js @@ -0,0 +1,2869 @@ +// Last time updated at Monday, January 11th, 2016, 1:38:19 PM + +// _______________ +// Canvas-Designer + +// https://github.com/muaz-khan/Canvas-Designer + +'use strict'; + +(function() { + + // ------------------------------------------------------------- + var is = { + isLine: false, + isArc: false, + isDragLastPath: false, + isDragAllPaths: false, + isRectangle: false, + isQuadraticCurve: false, + isBezierCurve: false, + isPencil: false, + isEraser: false, + isText: false, + isImage: false, + + set: function(shape) { + var cache = this; + + cache.isLine = cache.isArc = cache.isDragLastPath = cache.isDragAllPaths = cache.isRectangle = cache.isQuadraticCurve = cache.isBezierCurve = cache.isPencil = cache.isEraser = cache.isText = cache.isImage = false; + cache['is' + shape] = true; + } + }; + + // ------------------------------------------------------------- + + function addEvent(element, eventType, callback) { + if (element.addEventListener) { + element.addEventListener(eventType, callback, !1); + return true; + } else if (element.attachEvent) return element.attachEvent('on' + eventType, callback); + else element['on' + eventType] = callback; + return this; + } + + // ------------------------------------------------------------- + + function find(selector) { + return document.getElementById(selector); + } + + // ------------------------------------------------------------- + + var points = [], + textarea = find('code-text'), + lineWidth = 5, + strokeStyle = 'red', + fillStyle = 'white', + globalAlpha = 1, + globalCompositeOperation = 'source-over', + lineCap = 'round', + font = '35px Verdana', + lineJoin = 'round'; + + // ------------------------------------------------------------- + + function getContext(id) { + var canv = find(id), + ctx = canv.getContext('2d'); + + canv.setAttribute('width', innerWidth); + canv.setAttribute('height', innerHeight); + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = strokeStyle; + ctx.fillStyle = fillStyle; + ctx.font = font; + + return ctx; + } + + // ------------------------------------------------------------- + + var context = getContext('main-canvas'), + tempContext = getContext('temp-canvas'); + + window.canvasElementToBeRecorded = tempContext.canvas; + tempContext.clearRect = function() {}; + + // ------------------------------------------------------------- + + var common = { + + // ------------------------------------------------------------- + + updateTextArea: function() { + var c = common, + toFixed = c.toFixed, + getPoint = c.getPoint, + + isAbsolutePoints = find('is-absolute-points').checked, + isShortenCode = find('is-shorten-code').checked; + + if (isAbsolutePoints && isShortenCode) c.absoluteShortened(); + if (isAbsolutePoints && !isShortenCode) c.absoluteNOTShortened(toFixed); + if (!isAbsolutePoints && isShortenCode) c.relativeShortened(toFixed, getPoint); + if (!isAbsolutePoints && !isShortenCode) c.relativeNOTShortened(toFixed, getPoint); + }, + + // ------------------------------------------------------------- + + toFixed: function(input) { + return Number(input).toFixed(1); + }, + + // ------------------------------------------------------------- + + getPoint: function(pointToCompare, compareWith, prefix) { + if (pointToCompare > compareWith) pointToCompare = prefix + ' + ' + (pointToCompare - compareWith); + else if (pointToCompare < compareWith) pointToCompare = prefix + ' - ' + (compareWith - pointToCompare); + else pointToCompare = prefix; + + return pointToCompare; + }, + + + + // ------------------------------------------------------------- + + absoluteShortened: function() { + + var output = '', + length = points.length, + i = 0, + point; + for (i; i < length; i++) { + point = points[i]; + output += this.shortenHelper(point[0], point[1], point[2]); + } + + output = output.substr(0, output.length - 2); + textarea.value = 'var points = [' + output + '], length = points.length, point, p, i = 0;\n\n' + this.forLoop; + + this.prevProps = null; + }, + + // ------------------------------------------------------------- + + absoluteNOTShortened: function(toFixed) { + var tempArray = [], + i, point, p; + + for (i = 0; i < points.length; i++) { + p = points[i]; + point = p[1]; + + if (p[0] === 'pencil') { + tempArray[i] = ['context.beginPath();\n' + 'context.moveTo(' + point[0] + ', ' + point[1] + ');\n' + 'context.lineTo(' + point[2] + ', ' + point[3] + ');\n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'eraser') { + tempArray[i] = ['context.beginPath();\n' + 'context.moveTo(' + point[0] + ', ' + point[1] + ');\n' + 'context.lineTo(' + point[2] + ', ' + point[3] + ');\n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'line') { + tempArray[i] = ['context.beginPath();\n' + 'context.moveTo(' + point[0] + ', ' + point[1] + ');\n' + 'context.lineTo(' + point[2] + ', ' + point[3] + ');\n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'text') { + tempArray[i] = ['context.fillText(' + point[0] + ', ' + point[1] + ', ' + point[2] + ');\n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'arc') { + tempArray[i] = ['context.beginPath(); \n' + 'context.arc(' + toFixed(point[0]) + ',' + toFixed(point[1]) + ',' + toFixed(point[2]) + ',' + toFixed(point[3]) + ', 0,' + point[4] + '); \n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'rect') { + tempArray[i] = [this.strokeOrFill(p[2]) + '\n' + 'context.strokeRect(' + point[0] + ', ' + point[1] + ',' + point[2] + ',' + point[3] + ');\n' + 'context.fillRect(' + point[0] + ', ' + point[1] + ',' + point[2] + ',' + point[3] + ');']; + } + + if (p[0] === 'quadratic') { + tempArray[i] = ['context.beginPath();\n' + 'context.moveTo(' + point[0] + ', ' + point[1] + ');\n' + 'context.quadraticCurveTo(' + point[2] + ', ' + point[3] + ', ' + point[4] + ', ' + point[5] + ');\n' + this.strokeOrFill(p[2])]; + } + + if (p[0] === 'bezier') { + tempArray[i] = ['context.beginPath();\n' + 'context.moveTo(' + point[0] + ', ' + point[1] + ');\n' + 'context.bezierCurveTo(' + point[2] + ', ' + point[3] + ', ' + point[4] + ', ' + point[5] + ', ' + point[6] + ', ' + point[7] + ');\n' + this.strokeOrFill(p[2])]; + } + + } + textarea.value = tempArray.join('\n\n') + this.strokeFillText; + + this.prevProps = null; + }, + + // ------------------------------------------------------------- + + relativeShortened: function(toFixed, getPoint) { + var i = 0, + point, p, length = points.length, + output = '', + x = 0, + y = 0; + + for (i; i < length; i++) { + p = points[i]; + point = p[1]; + + if (i === 0) { + x = point[0]; + y = point[1]; + } + + if (p[0] === 'text') { + x = point[1]; + y = point[2]; + } + + if (p[0] === 'pencil') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y') + ], p[2]); + } + + if (p[0] === 'eraser') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y') + ], p[2]); + } + + if (p[0] === 'line') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y') + ], p[2]); + } + + if (p[0] === 'text') { + output += this.shortenHelper(p[0], [ + point[0], + getPoint(point[1], x, 'x'), + getPoint(point[2], y, 'y') + ], p[2]); + } + + if (p[0] === 'arc') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + point[2], + point[3], + point[4] + ], p[2]); + } + + if (p[0] === 'rect') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y') + ], p[2]); + } + + if (p[0] === 'quadratic') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y'), + getPoint(point[4], x, 'x'), + getPoint(point[5], y, 'y') + ], p[2]); + } + + if (p[0] === 'bezier') { + output += this.shortenHelper(p[0], [ + getPoint(point[0], x, 'x'), + getPoint(point[1], y, 'y'), + getPoint(point[2], x, 'x'), + getPoint(point[3], y, 'y'), + getPoint(point[4], x, 'x'), + getPoint(point[5], y, 'y'), + getPoint(point[6], x, 'x'), + getPoint(point[7], y, 'y') + ], p[2]); + } + } + + output = output.substr(0, output.length - 2); + textarea.value = 'var x = ' + x + ', y = ' + y + ', points = [' + output + '], length = points.length, point, p, i = 0;\n\n' + this.forLoop; + + this.prevProps = null; + }, + + // ------------------------------------------------------------- + + relativeNOTShortened: function(toFixed, getPoint) { + var i, point, p, length = points.length, + output = '', + x = 0, + y = 0; + + for (i = 0; i < length; i++) { + p = points[i]; + point = p[1]; + + if (i === 0) { + x = point[0]; + y = point[1]; + + if (p[0] === 'text') { + x = point[1]; + y = point[2]; + } + + output = 'var x = ' + x + ', y = ' + y + ';\n\n'; + } + + if (p[0] === 'arc') { + output += 'context.beginPath();\n' + 'context.arc(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ', ' + point[2] + ', ' + point[3] + ', 0, ' + point[4] + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (p[0] === 'pencil') { + output += 'context.beginPath();\n' + 'context.moveTo(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ');\n' + 'context.lineTo(' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (p[0] === 'eraser') { + output += 'context.beginPath();\n' + 'context.moveTo(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ');\n' + 'context.lineTo(' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (p[0] === 'line') { + output += 'context.beginPath();\n' + 'context.moveTo(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ');\n' + 'context.lineTo(' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (p[0] === 'text') { + output += 'context.fillText(' + point[0] + ', ' + getPoint(point[1], x, 'x') + ', ' + getPoint(point[2], y, 'y') + ');\n' + this.strokeOrFill(p[2]); + } + + if (p[0] === 'rect') { + output += this.strokeOrFill(p[2]) + '\n' + 'context.strokeRect(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ', ' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ');\n' + 'context.fillRect(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ', ' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ');'; + } + + if (p[0] === 'quadratic') { + output += 'context.beginPath();\n' + 'context.moveTo(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ');\n' + 'context.quadraticCurveTo(' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ', ' + getPoint(point[4], x, 'x') + ', ' + getPoint(point[5], y, 'y') + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (p[0] === 'bezier') { + output += 'context.beginPath();\n' + 'context.moveTo(' + getPoint(point[0], x, 'x') + ', ' + getPoint(point[1], y, 'y') + ');\n' + 'context.bezierCurveTo(' + getPoint(point[2], x, 'x') + ', ' + getPoint(point[3], y, 'y') + ', ' + getPoint(point[4], x, 'x') + ', ' + getPoint(point[5], y, 'y') + ', ' + getPoint(point[6], x, 'x') + ', ' + getPoint(point[7], y, 'y') + ');\n' + + + this.strokeOrFill(p[2]); + } + + if (i !== length - 1) output += '\n\n'; + } + textarea.value = output + this.strokeFillText; + + this.prevProps = null; + }, + + // ------------------------------------------------------------- + + forLoop: 'for(i; i < length; i++) {\n' + '\t p = points[i];\n' + '\t point = p[1];\n' + '\t context.beginPath();\n\n' + + // ------------------------------------------------------------- + + '\t if(p[2]) { \n' + '\t\t context.lineWidth = p[2][0];\n' + '\t\t context.strokeStyle = p[2][1];\n' + '\t\t context.fillStyle = p[2][2];\n' + + + '\t\t context.globalAlpha = p[2][3];\n' + '\t\t context.globalCompositeOperation = p[2][4];\n' + '\t\t context.lineCap = p[2][5];\n' + '\t\t context.lineJoin = p[2][6];\n' + '\t\t context.font = p[2][7];\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "line") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "pencil") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "text") { \n' + '\t\t context.fillText(point[0], point[1], point[2]);\n' + '\t }\n\n' + + + // ------------------------------------------------------------- + + + '\t if(p[0] === "eraser") { \n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.lineTo(point[2], point[3]);\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "arc") context.arc(point[0], point[1], point[2], point[3], 0, point[4]); \n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "rect") {\n' + '\t\t context.strokeRect(point[0], point[1], point[2], point[3]);\n' + '\t\t context.fillRect(point[0], point[1], point[2], point[3]);\n' + + + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "quadratic") {\n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.quadraticCurveTo(point[2], point[3], point[4], point[5]);\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t if(p[0] === "bezier") {\n' + '\t\t context.moveTo(point[0], point[1]);\n' + '\t\t context.bezierCurveTo(point[2], point[3], point[4], point[5], point[6], point[7]);\n' + '\t }\n\n' + + // ------------------------------------------------------------- + + + '\t context.stroke();\n' + '\t context.fill();\n' + + + '}', + + // ------------------------------------------------------------- + + strokeFillText: '\n\nfunction strokeOrFill(lineWidth, strokeStyle, fillStyle, globalAlpha, globalCompositeOperation, lineCap, lineJoin, font) { \n' + '\t if(lineWidth) { \n' + '\t\t context.globalAlpha = globalAlpha;\n' + '\t\t context.globalCompositeOperation = globalCompositeOperation;\n' + '\t\t context.lineCap = lineCap;\n' + '\t\t context.lineJoin = lineJoin;\n' + + + '\t\t context.lineWidth = lineWidth;\n' + '\t\t context.strokeStyle = strokeStyle;\n' + '\t\t context.fillStyle = fillStyle;\n' + '\t\t context.font = font;\n' + '\t } \n\n' + + + '\t context.stroke();\n' + '\t context.fill();\n' + + + '}', + + // ------------------------------------------------------------- + + strokeOrFill: function(p) { + if (!this.prevProps || this.prevProps !== p.join(',')) { + this.prevProps = p.join(','); + + return 'strokeOrFill("' + p.join('", "') + '");'; + } + + return 'strokeOrFill();'; + }, + + // ------------------------------------------------------------- + prevProps: null, + shortenHelper: function(name, p1, p2) { + var result = '["' + name + '", [' + p1.join(', ') + ']'; + + if (!this.prevProps || this.prevProps !== p2.join(',')) { + this.prevProps = p2.join(','); + result += ', ["' + p2.join('", "') + '"]'; + } + + return result + '], '; + } + + // ------------------------------------------------------------- + + }; + + // ------------------------------------------------------------- + + function endLastPath() { + var cache = is; + + if (cache.isArc) arcHandler.end(); + else if (cache.isQuadraticCurve) quadraticHandler.end(); + else if (cache.isBezierCurve) bezierHandler.end(); + + drawHelper.redraw(); + } + + // ------------------------------------------------------------- + + var copiedStuff = [], + isControlKeyPressed; + + // ------------------------------------------------------------- + + function copy() { + endLastPath(); + + dragHelper.global.startingIndex = 0; + + if (find('copy-last').checked) { + copiedStuff = points[points.length - 1]; + setSelection(find('drag-last-path'), 'DragLastPath'); + } else { + copiedStuff = points; + setSelection(find('drag-all-paths'), 'DragAllPaths'); + } + } + + // ------------------------------------------------------------- + + function paste() { + endLastPath(); + + dragHelper.global.startingIndex = 0; + + if (find('copy-last').checked) { + points[points.length] = copiedStuff; + + dragHelper.global = { + prevX: 0, + prevY: 0, + startingIndex: points.length - 1 + }; + + dragHelper.dragAllPaths(0, 0); + setSelection(find('drag-last-path'), 'DragLastPath'); + } else { + + dragHelper.global.startingIndex = points.length; + points = points.concat(copiedStuff); + setSelection(find('drag-all-paths'), 'DragAllPaths'); + } + } + + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + var tools = { + line: true, + pencil: true, + dragSingle: true, + dragMultiple: true, + eraser: true, + rectangle: true, + arc: true, + bezier: true, + quadratic: true, + text: true + }; + + if (params.tools) { + tools = JSON.parse(params.tools); + } + + function setSelection(element, prop) { + endLastPath(); + hideContainers(); + + is.set(prop); + + var selected = document.getElementsByClassName('selected-shape')[0]; + if (selected) selected.className = selected.className.replace(/selected-shape/g, ''); + + element.className += ' selected-shape'; + } + + // ------------------------------------------------------------- + + (function() { + + var cache = {}; + + var lineCapSelect = find('lineCap-select'); + var lineJoinSelect = find('lineJoin-select'); + + // ------------------------------------------------------------- + + function getContext(id) { + var context = find(id).getContext('2d'); + context.lineWidth = 2; + context.strokeStyle = '#6c96c8'; + return context; + } + + // ------------------------------------------------------------- + + function bindEvent(context, shape) { + if (shape === 'Pencil') { + lineCap = lineJoin = 'round'; + } + + /* Default: setting default selected shape!! */ + if (params.selectedIcon) { + params.selectedIcon = params.selectedIcon.split('')[0].toUpperCase() + params.selectedIcon.replace(params.selectedIcon.split('').shift(1), ''); + if (params.selectedIcon === shape) { + is.set(params.selectedIcon); + } + } else is.set('Pencil'); + + addEvent(context.canvas, 'click', function() { + + dragHelper.global.startingIndex = 0; + + setSelection(this, shape); + + if (this.id === 'drag-last-path') { + find('copy-last').checked = true; + find('copy-all').checked = false; + } else if (this.id === 'drag-all-paths') { + find('copy-all').checked = true; + find('copy-last').checked = false; + } + + if (this.id === 'image-icon') { + var selector = new FileSelector(); + selector.selectSingleFile(function(file) { + if (!file) return; + + var reader = new FileReader(); + reader.onload = function(event) { + var image = new Image(); + image.onload = function() { + var index = imageHandler.images.length; + + imageHandler.lastImageURL = image.src; + imageHandler.lastImageIndex = index; + + imageHandler.images.push(image); + }; + image.src = event.target.result; + }; + reader.readAsDataURL(file); + }); + } + + if (this.id === 'pencil-icon' || this.id === 'eraser-icon') { + cache.lineCap = lineCap; + cache.lineJoin = lineJoin; + + lineCap = lineJoin = 'round'; + } else if (cache.lineCap && cache.lineJoin) { + lineCap = cache.lineCap; + lineJoin = cache.lineJoin; + } + + if (this.id === 'eraser-icon') { + cache.strokeStyle = strokeStyle; + cache.fillStyle = fillStyle; + cache.lineWidth = lineWidth; + + strokeStyle = 'White'; + fillStyle = 'White'; + lineWidth = 10; + } else if (cache.strokeStyle && cache.fillStyle && typeof cache.lineWidth !== 'undefined') { + strokeStyle = cache.strokeStyle; + fillStyle = cache.fillStyle; + lineWidth = cache.lineWidth; + } + }); + } + + // ------------------------------------------------------------- + + var toolBox = find('tool-box'); + toolBox.style.height = (innerHeight /* - toolBox.offsetTop - 77 */ ) + 'px'; + + // ------------------------------------------------------------- + + + function decorateDragLastPath() { + var context = getContext('drag-last-path'); + + var x = 10, + y = 6, + line = "line", + points = [ + [line, x, y, x + 5, y + 27], + [line, x, y, x + 18, y + 19], + [line, x + 17, y + 19, x + 9, y + 20], + [line, x + 9, y + 20, x + 5, y + 27], + [line, x + 16, y + 22, x + 16, y + 31], + [line, x + 12, y + 27, x + 20, y + 27] + ], + length = points.length, + point, i; + + for (i = 0; i < length; i++) { + point = points[i]; + + if (point[0] === "line") { + context.beginPath(); + context.moveTo(point[1], point[2]); + context.lineTo(point[3], point[4]); + context.closePath(); + context.stroke(); + } + } + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Last', 18, 12); + + bindEvent(context, 'DragLastPath'); + } + + if (tools.dragSingle === true) { + decorateDragLastPath(); + } else document.getElementById('drag-last-path').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateDragAllPaths() { + var context = getContext('drag-all-paths'); + + var x = 10, + y = 6, + line = "line", + points = [ + [line, x, y, x + 5, y + 27], + [line, x, y, x + 18, y + 19], + [line, x + 17, y + 19, x + 9, y + 20], + [line, x + 9, y + 20, x + 5, y + 27], + [line, x + 16, y + 22, x + 16, y + 31], + [line, x + 12, y + 27, x + 20, y + 27] + ], + length = points.length, + point, i; + + for (i = 0; i < length; i++) { + point = points[i]; + + if (point[0] === "line") { + context.beginPath(); + context.moveTo(point[1], point[2]); + context.lineTo(point[3], point[4]); + context.closePath(); + context.stroke(); + } + } + + context.fillStyle = 'Gray'; + context.font = '10px Verdana'; + context.fillText('All', 20, 12); + + bindEvent(context, 'DragAllPaths'); + } + + if (tools.dragMultiple === true) { + decorateDragAllPaths(); + } else document.getElementById('drag-all-paths').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateLine() { + var context = getContext('line'); + + context.moveTo(0, 0); + context.lineTo(40, 40); + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Line', 16, 12); + + bindEvent(context, 'Line'); + } + + if (tools.line === true) { + decorateLine(); + } else document.getElementById('line').style.display = 'none'; + + // ------------------------------------------------------------- + + function decoratePencil() { + var context = getContext('pencil-icon'); + + context.lineWidth = 5; + context.lineCap = 'round'; + context.moveTo(35, 20); + context.lineTo(5, 35); + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Pencil', 6, 12); + + bindEvent(context, 'Pencil'); + } + + if (tools.pencil === true) { + decoratePencil(); + } else document.getElementById('pencil-icon').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateEraser() { + var context = getContext('eraser-icon'); + + context.lineWidth = 9; + context.lineCap = 'round'; + context.moveTo(35, 20); + context.lineTo(5, 25); + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Eraser', 6, 12); + + bindEvent(context, 'Eraser'); + } + + if (tools.eraser === true) { + decorateEraser(); + } else document.getElementById('eraser-icon').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateText() { + var context = getContext('text-icon'); + + context.font = '22px Verdana'; + context.strokeText('T', 15, 30); + + bindEvent(context, 'Text'); + } + + if (tools.text === true) { + decorateText(); + } else document.getElementById('text-icon').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateImage() { + var context = getContext('image-icon'); + + var image = new Image(); + image.onload = function() { + context.drawImage(image, 4, 4, 32, 32); + bindEvent(context, 'Image'); + }; + image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAADFBMVEVYWFhVVVUAAABUVFTqqlXjAAAAA3RSTlMxdACUjPeLAAAATElEQVR42u3SQQrAMAwDQSn7/z+XFExTcOxroN3zgC4STecApy1gpP2gBgZXQMwKwJ23QITYACLlQBC9gAFNwJMXoJhVc7lBA/gsuAArEgqPcT12VgAAAABJRU5ErkJggg=='; + } + + if (tools.image === true) { + decorateImage(); + } else document.getElementById('image-icon').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateArc() { + var context = getContext('arc'); + + context.arc(20, 20, 16.3, Math.PI * 2, 0, 1); + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Arc', 10, 24); + + bindEvent(context, 'Arc'); + } + + if (tools.arc === true) { + decorateArc(); + } else document.getElementById('arc').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateRect() { + var context = getContext('rectangle'); + + context.strokeRect(5, 5, 30, 30); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Rect', 8, 24); + + bindEvent(context, 'Rectangle'); + } + + if (tools.rectangle === true) { + decorateRect(); + } else document.getElementById('rectangle').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateQuadratic() { + var context = getContext('quadratic-curve'); + + context.moveTo(0, 0); + context.quadraticCurveTo(50, 10, 30, 40); + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('quad..', 2, 24); + + bindEvent(context, 'QuadraticCurve'); + } + + if (tools.quadratic === true) { + decorateQuadratic(); + } else document.getElementById('quadratic-curve').style.display = 'none'; + + // ------------------------------------------------------------- + + function decorateBezier() { + var context = getContext('bezier-curve'); + + var x = 0, + y = 4; + + context.moveTo(x, y); + context.bezierCurveTo(x + 86, y + 16, x - 45, y + 24, x + 48, y + 34); + + context.stroke(); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Bezier', 10, 8); + + bindEvent(context, 'BezierCurve'); + } + + if (tools.bezier === true) { + decorateBezier(); + } else document.getElementById('bezier-curve').style.display = 'none'; + + // ------------------------------------------------------------- + + function tempStrokeTheLine(context, width, mx, my, lx, ly) { + context.beginPath(); + context.lineWidth = width; + context.moveTo(mx, my); + context.lineTo(lx, ly); + context.stroke(); + } + + function decorateLineWidth() { + var context = getContext('line-width'); + + tempStrokeTheLine(context, 2, 5, 15, 35, 15); + tempStrokeTheLine(context, 3, 5, 20, 35, 20); + tempStrokeTheLine(context, 4, 5, 26, 35, 26); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Line', 8, 12); + context.fillText('Width', 6, 38); + + var lineWidthContainer = find('line-width-container'), + lineWidthText = find('line-width-text'), + btnLineWidthDone = find('line-width-done'), + h1 = document.getElementsByTagName('h1')[0], + canvas = context.canvas; + + addEvent(canvas, 'click', function() { + hideContainers(); + + lineWidthContainer.style.display = 'block'; + lineWidthContainer.style.top = (canvas.offsetTop + 1) + 'px'; + lineWidthContainer.style.left = (canvas.offsetLeft + canvas.clientWidth) + 'px'; + + lineWidthText.focus(); + }); + + addEvent(btnLineWidthDone, 'click', function() { + lineWidthContainer.style.display = 'none'; + lineWidth = lineWidthText.value; + }); + } + + decorateLineWidth(); + + // ------------------------------------------------------------- + function decorateColors() { + var context = getContext('colors'); + + context.fillStyle = 'red'; + context.fillRect(5, 3, 30, 10); + + context.fillStyle = 'green'; + context.fillRect(5, 15, 30, 10); + + context.fillStyle = 'blue'; + context.fillRect(5, 27, 30, 10); + + var colorsContainer = find('colors-container'), + strokeStyleText = find('stroke-style'), + fillStyleText = find('fill-style'), + btnColorsDone = find('colors-done'), + h1 = document.getElementsByTagName('h1')[0], + canvas = context.canvas; + + addEvent(canvas, 'click', function() { + hideContainers(); + + colorsContainer.style.display = 'block'; + colorsContainer.style.top = (canvas.offsetTop + 1) + 'px'; + colorsContainer.style.left = (canvas.offsetLeft + canvas.clientWidth) + 'px'; + + strokeStyleText.focus(); + }); + + addEvent(btnColorsDone, 'click', function() { + colorsContainer.style.display = 'none'; + strokeStyle = strokeStyleText.value; + fillStyle = fillStyleText.value; + }); + } + + decorateColors(); + + // ------------------------------------------------------------- + function decorateAdditionalOptions() { + var context = getContext('additional'); + + context.fillStyle = '#6c96c8'; + context.font = '35px Verdana'; + context.fillText('»', 10, 27); + + context.fillStyle = 'Gray'; + context.font = '9px Verdana'; + context.fillText('Extras!', 2, 38); + + var additionalContainer = find('additional-container'), + btnAdditionalClose = find('additional-close'), + h1 = document.getElementsByTagName('h1')[0], + canvas = context.canvas, + globalAlphaSelect = find('globalAlpha-select'), + globalCompositeOperationSelect = find('globalCompositeOperation-select'); + + addEvent(canvas, 'click', function() { + hideContainers(); + + additionalContainer.style.display = 'block'; + additionalContainer.style.top = (canvas.offsetTop + 1) + 'px'; + additionalContainer.style.left = (canvas.offsetLeft + canvas.clientWidth) + 'px'; + }); + + addEvent(btnAdditionalClose, 'click', function() { + additionalContainer.style.display = 'none'; + + globalAlpha = globalAlphaSelect.value; + globalCompositeOperation = globalCompositeOperationSelect.value; + lineCap = lineCapSelect.value; + lineJoin = lineJoinSelect.value; + }); + } + + decorateAdditionalOptions(); + + // ------------------------------------------------------------- + + var designPreview = find('design-preview'), + codePreview = find('code-preview'); + + // ------------------------------------------------------------- + + // todo: use this function in share-drawings.js + // to sync buttons' states + window.selectBtn = function(btn, isSkipWebRTCMessage) { + codePreview.className = designPreview.className = ''; + + if (btn == designPreview) designPreview.className = 'preview-selected'; + else codePreview.className = 'preview-selected'; + + if (!isSkipWebRTCMessage && window.connection && connection.numberOfConnectedUsers >= 1) { + connection.send({ + btnSelected: btn.id + }); + } else { + // to sync buttons' UI-states + if (btn == designPreview) btnDesignerPreviewClicked(); + else btnCodePreviewClicked(); + } + }; + + // ------------------------------------------------------------- + + addEvent(designPreview, 'click', function() { + selectBtn(designPreview); + btnDesignerPreviewClicked(); + }); + + function btnDesignerPreviewClicked() { + codeText.parentNode.style.display = 'none'; + optionsContainer.style.display = 'none'; + + hideContainers(); + endLastPath(); + } + + // ------------------------------------------------------------- + + addEvent(codePreview, 'click', function() { + selectBtn(codePreview); + btnCodePreviewClicked(); + }); + + function btnCodePreviewClicked() { + codeText.parentNode.style.display = 'block'; + optionsContainer.style.display = 'block'; + + codeText.focus(); + common.updateTextArea(); + + setHeightForCodeAndOptionsContainer(); + + hideContainers(); + endLastPath(); + } + + // ------------------------------------------------------------- + + var codeText = find('code-text'), + optionsContainer = find('options-container'); + + // ------------------------------------------------------------- + + function setHeightForCodeAndOptionsContainer() { + codeText.style.width = (innerWidth - optionsContainer.clientWidth - 30) + 'px'; + codeText.style.height = (innerHeight - 40) + 'px'; + + codeText.style.marginLeft = (optionsContainer.clientWidth) + 'px'; + optionsContainer.style.height = (innerHeight) + 'px'; + } + + // ------------------------------------------------------------- + + var isAbsolute = find('is-absolute-points'), + isShorten = find('is-shorten-code'); + + addEvent(isShorten, 'change', common.updateTextArea); + addEvent(isAbsolute, 'change', common.updateTextArea); + + // ------------------------------------------------------------- + + })(); + + // ------------------------------------------------------------- + + function hideContainers() { + var additionalContainer = find('additional-container'), + colorsContainer = find('colors-container'), + lineWidthContainer = find('line-width-container'); + + additionalContainer.style.display = colorsContainer.style.display = lineWidthContainer.style.display = 'none'; + } + + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + var drawHelper = { + + // ------------------------------------------------------------- + + redraw: function(skipSync) { + return; + context.clearRect(0, 0, innerWidth, innerHeight); + + var i, point, length = points.length; + for (i = 0; i < length; i++) { + if(i == 0 && window.screenVideoElement) { + context.drawImage(window.screenVideoElement, 0, -50, context.canvas.width, context.canvas.height); + } + else { + point = points[i]; + this[point[0]](context, point[1], point[2]); + } + } + }, + + // ------------------------------------------------------------- + + getOptions: function() { + return [lineWidth, strokeStyle, fillStyle, globalAlpha, globalCompositeOperation, lineCap, lineJoin, font]; + }, + + // ------------------------------------------------------------- + + handleOptions: function(context, opt, isNoFillStroke) { + opt = opt || this.getOptions(); + + context.globalAlpha = opt[3]; + context.globalCompositeOperation = opt[4]; + + context.lineCap = opt[5]; + context.lineJoin = opt[6]; + context.lineWidth = opt[0]; + + context.strokeStyle = opt[1]; + context.fillStyle = opt[2]; + + if (!isNoFillStroke) { + context.stroke(); + context.fill(); + } + }, + + // ------------------------------------------------------------- + + line: function(context, point, options) { + context.beginPath(); + context.moveTo(point[0], point[1]); + context.lineTo(point[2], point[3]); + + this.handleOptions(context, options); + }, + + // ------------------------------------------------------------- + + text: function(context, point, options) { + var oldFillStyle = fillStyle; + context.fillStyle = fillStyle === 'transparent' || fillStyle === 'White' ? 'Black' : fillStyle; + context.font = '15px Verdana'; + context.fillText(point[0].substr(1, point[0].length - 2), point[1], point[2]); + fillStyle = oldFillStyle; + + this.handleOptions(context, options); + }, + + // ------------------------------------------------------------- + + arc: function(context, point, options) { + context.beginPath(); + context.arc(point[0], point[1], point[2], point[3], 0, point[4]); + + this.handleOptions(context, options); + }, + + // ------------------------------------------------------------- + + rect: function(context, point, options) { + this.handleOptions(context, options, true); + + context.strokeRect(point[0], point[1], point[2], point[3]); + context.fillRect(point[0], point[1], point[2], point[3]); + }, + + // ------------------------------------------------------------- + + image: function(context, point, options) { + this.handleOptions(context, options, true); + + var image = imageHandler.images[point[5]]; + if (!image) { + var image = new Image(); + image.onload = function() { + var index = imageHandler.images.length; + + imageHandler.lastImageURL = image.src; + imageHandler.lastImageIndex = index; + + imageHandler.images.push(image); + context.drawImage(image, point[1], point[2], point[3], point[4]); + }; + image.src = point[0]; + return; + } + + context.drawImage(image, point[1], point[2], point[3], point[4]); + }, + + // ------------------------------------------------------------- + + quadratic: function(context, point, options) { + context.beginPath(); + context.moveTo(point[0], point[1]); + context.quadraticCurveTo(point[2], point[3], point[4], point[5]); + + this.handleOptions(context, options); + }, + + // ------------------------------------------------------------- + + bezier: function(context, point, options) { + context.beginPath(); + context.moveTo(point[0], point[1]); + context.bezierCurveTo(point[2], point[3], point[4], point[5], point[6], point[7]); + + this.handleOptions(context, options); + } + + // ------------------------------------------------------------- + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + var dragHelper = { + + // ------------------------------------------------------------- + + global: { + prevX: 0, + prevY: 0, + ismousedown: false, + pointsToMove: 'all', + startingIndex: 0 + }, + + // ------------------------------------------------------------- + + mousedown: function(e) { + + // ------------------------------------------------------------- + + if (isControlKeyPressed) { + copy(); + paste(); + isControlKeyPressed = false; + } + + // ------------------------------------------------------------- + + var dHelper = dragHelper, + g = dHelper.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + g.prevX = x; + g.prevY = y; + + g.pointsToMove = 'all'; + + if (points.length) { + var p = points[points.length - 1], + point = p[1]; + + if (p[0] === 'line') { + + if (dHelper.isPointInPath(x, y, point[0], point[1])) { + g.pointsToMove = 'head'; + } + + if (dHelper.isPointInPath(x, y, point[2], point[3])) { + g.pointsToMove = 'tail'; + } + } + + if (p[0] === 'rect') { + + if (dHelper.isPointInPath(x, y, point[0], point[1])) { + g.pointsToMove = 'stretch-first'; + } + + if (dHelper.isPointInPath(x, y, point[0] + point[2], point[1])) { + g.pointsToMove = 'stretch-second'; + } + + if (dHelper.isPointInPath(x, y, point[0], point[1] + point[3])) { + g.pointsToMove = 'stretch-third'; + } + + if (dHelper.isPointInPath(x, y, point[0] + point[2], point[1] + point[3])) { + g.pointsToMove = 'stretch-last'; + } + } + + if (p[0] === 'image') { + + if (dHelper.isPointInPath(x, y, point[1], point[2])) { + g.pointsToMove = 'stretch-first'; + } + + if (dHelper.isPointInPath(x, y, point[1] + point[3], point[2])) { + g.pointsToMove = 'stretch-second'; + } + + if (dHelper.isPointInPath(x, y, point[1], point[2] + point[4])) { + g.pointsToMove = 'stretch-third'; + } + + if (dHelper.isPointInPath(x, y, point[1] + point[3], point[2] + point[4])) { + g.pointsToMove = 'stretch-last'; + } + } + + if (p[0] === 'quadratic') { + + if (dHelper.isPointInPath(x, y, point[0], point[1])) { + g.pointsToMove = 'starting-points'; + } + + if (dHelper.isPointInPath(x, y, point[2], point[3])) { + g.pointsToMove = 'control-points'; + } + + if (dHelper.isPointInPath(x, y, point[4], point[5])) { + g.pointsToMove = 'ending-points'; + } + } + + if (p[0] === 'bezier') { + + if (dHelper.isPointInPath(x, y, point[0], point[1])) { + g.pointsToMove = 'starting-points'; + } + + if (dHelper.isPointInPath(x, y, point[2], point[3])) { + g.pointsToMove = '1st-control-points'; + } + + if (dHelper.isPointInPath(x, y, point[4], point[5])) { + g.pointsToMove = '2nd-control-points'; + } + + if (dHelper.isPointInPath(x, y, point[6], point[7])) { + g.pointsToMove = 'ending-points'; + } + } + } + + g.ismousedown = true; + }, + + // ------------------------------------------------------------- + + mouseup: function() { + var g = this.global; + + if (is.isDragLastPath) { + tempContext.clearRect(0, 0, innerWidth, innerHeight); + this.end(); + } + + g.ismousedown = false; + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop, + g = this.global; + + drawHelper.redraw(); + + if (g.ismousedown) { + this.dragShape(x, y); + } + + if (is.isDragLastPath) this.init(); + }, + + // ------------------------------------------------------------- + + init: function() { + if (!points.length) return; + + var p = points[points.length - 1], + point = p[1], + g = this.global; + + if (g.ismousedown) tempContext.fillStyle = 'rgba(255,85 ,154,.9)'; + else tempContext.fillStyle = 'rgba(255,85 ,154,.4)'; + + if (p[0] === 'quadratic') { + + tempContext.beginPath(); + + tempContext.arc(point[0], point[1], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[2], point[3], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[4], point[5], 10, Math.PI * 2, 0, !1); + + tempContext.fill(); + } + + if (p[0] === 'bezier') { + + tempContext.beginPath(); + + tempContext.arc(point[0], point[1], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[2], point[3], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[4], point[5], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[6], point[7], 10, Math.PI * 2, 0, !1); + + tempContext.fill(); + } + + if (p[0] === 'line') { + + tempContext.beginPath(); + + tempContext.arc(point[0], point[1], 10, Math.PI * 2, 0, !1); + tempContext.arc(point[2], point[3], 10, Math.PI * 2, 0, !1); + + tempContext.fill(); + } + + if (p[0] === 'text') { + tempContext.font = "15px Verdana"; + tempContext.fillText(point[0], point[1], point[2]); + } + + if (p[0] === 'rect') { + + tempContext.beginPath(); + tempContext.arc(point[0], point[1], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[0] + point[2], point[1], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[0], point[1] + point[3], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[0] + point[2], point[1] + point[3], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + } + + if (p[0] === 'image') { + tempContext.beginPath(); + tempContext.arc(point[1], point[2], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[1] + point[3], point[2], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[1], point[2] + point[4], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + + tempContext.beginPath(); + tempContext.arc(point[1] + point[3], point[2] + point[4], 10, Math.PI * 2, 0, !1); + tempContext.fill(); + } + }, + + // ------------------------------------------------------------- + + isPointInPath: function(x, y, first, second) { + return x > first - 10 && x < first + 10 && y > second - 10 && y < second + 10; + }, + + // ------------------------------------------------------------- + + getPoint: function(point, prev, otherPoint) { + if (point > prev) { + point = otherPoint + (point - prev); + } else { + point = otherPoint - (prev - point); + } + + return point; + }, + + // ------------------------------------------------------------- + + getXYWidthHeight: function(x, y, prevX, prevY, oldPoints) { + if (oldPoints.pointsToMove == 'stretch-first') { + if (x > prevX) { + oldPoints.x = oldPoints.x + (x - prevX); + oldPoints.width = oldPoints.width - (x - prevX); + } else { + oldPoints.x = oldPoints.x - (prevX - x); + oldPoints.width = oldPoints.width + (prevX - x); + } + + if (y > prevY) { + oldPoints.y = oldPoints.y + (y - prevY); + oldPoints.height = oldPoints.height - (y - prevY); + } else { + oldPoints.y = oldPoints.y - (prevY - y); + oldPoints.height = oldPoints.height + (prevY - y); + } + } + + if (oldPoints.pointsToMove == 'stretch-second') { + if (x > prevX) { + oldPoints.width = oldPoints.width + (x - prevX); + } else { + oldPoints.width = oldPoints.width - (prevX - x); + } + + if (y < prevY) { + oldPoints.y = oldPoints.y + (y - prevY); + oldPoints.height = oldPoints.height - (y - prevY); + } else { + oldPoints.y = oldPoints.y - (prevY - y); + oldPoints.height = oldPoints.height + (prevY - y); + } + } + + if (oldPoints.pointsToMove == 'stretch-third') { + if (x > prevX) { + oldPoints.x = oldPoints.x + (x - prevX); + oldPoints.width = oldPoints.width - (x - prevX); + } else { + oldPoints.x = oldPoints.x - (prevX - x); + oldPoints.width = oldPoints.width + (prevX - x); + } + + if (y < prevY) { + oldPoints.height = oldPoints.height + (y - prevY); + } else { + oldPoints.height = oldPoints.height - (prevY - y); + } + } + + return oldPoints; + }, + + // ------------------------------------------------------------- + + dragShape: function(x, y) { + if (!this.global.ismousedown) return; + + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + if (is.isDragLastPath) { + this.dragLastPath(x, y); + } + + if (is.isDragAllPaths) { + this.dragAllPaths(x, y); + } + + var g = this.global; + + g.prevX = x; + g.prevY = y; + }, + + // ------------------------------------------------------------- + + end: function() { + if (!points.length) return; + + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + var point = points[points.length - 1]; + drawHelper[point[0]](context, point[1], point[2]); + }, + + // ------------------------------------------------------------- + + dragAllPaths: function(x, y) { + var g = this.global, + prevX = g.prevX, + prevY = g.prevY, + p, point, + length = points.length, + getPoint = this.getPoint, + i = g.startingIndex; + + for (i; i < length; i++) { + p = points[i]; + point = p[1]; + + if (p[0] === 'line') { + points[i] = [p[0], + [ + getPoint(x, prevX, point[0]), + getPoint(y, prevY, point[1]), + getPoint(x, prevX, point[2]), + getPoint(y, prevY, point[3]) + ], p[2] + ]; + + } + + if (p[0] === 'text') { + points[i] = [p[0], + [ + point[0], + getPoint(x, prevX, point[1]), + getPoint(y, prevY, point[2]) + ], p[2] + ]; + } + + if (p[0] === 'arc') { + points[i] = [p[0], + [ + getPoint(x, prevX, point[0]), + getPoint(y, prevY, point[1]), + point[2], + point[3], + point[4] + ], p[2] + ]; + } + + if (p[0] === 'rect') { + points[i] = [p[0], + [ + getPoint(x, prevX, point[0]), + getPoint(y, prevY, point[1]), + point[2], + point[3] + ], p[2] + ]; + } + + if (p[0] === 'image') { + points[i] = [p[0], + [ + point[0], + getPoint(x, prevX, point[1]), + getPoint(y, prevY, point[2]), + point[3], + point[4], + point[5] + ], p[2] + ]; + } + + if (p[0] === 'quadratic') { + points[i] = [p[0], + [ + getPoint(x, prevX, point[0]), + getPoint(y, prevY, point[1]), + getPoint(x, prevX, point[2]), + getPoint(y, prevY, point[3]), + getPoint(x, prevX, point[4]), + getPoint(y, prevY, point[5]) + ], p[2] + ]; + } + + if (p[0] === 'bezier') { + points[i] = [p[0], + [ + getPoint(x, prevX, point[0]), + getPoint(y, prevY, point[1]), + getPoint(x, prevX, point[2]), + getPoint(y, prevY, point[3]), + getPoint(x, prevX, point[4]), + getPoint(y, prevY, point[5]), + getPoint(x, prevX, point[6]), + getPoint(y, prevY, point[7]) + ], p[2] + ]; + } + } + }, + + // ------------------------------------------------------------- + + dragLastPath: function(x, y) { + var g = this.global, + prevX = g.prevX, + prevY = g.prevY, + p = points[points.length - 1], + point = p[1], + getPoint = this.getPoint, + getXYWidthHeight = this.getXYWidthHeight, + isMoveAllPoints = g.pointsToMove === 'all'; + + if (p[0] === 'line') { + + if (g.pointsToMove === 'head' || isMoveAllPoints) { + point[0] = getPoint(x, prevX, point[0]); + point[1] = getPoint(y, prevY, point[1]); + } + + if (g.pointsToMove === 'tail' || isMoveAllPoints) { + point[2] = getPoint(x, prevX, point[2]); + point[3] = getPoint(y, prevY, point[3]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'text') { + + if (g.pointsToMove === 'head' || isMoveAllPoints) { + point[1] = getPoint(x, prevX, point[1]); + point[2] = getPoint(y, prevY, point[2]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'arc') { + point[0] = getPoint(x, prevX, point[0]); + point[1] = getPoint(y, prevY, point[1]); + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'rect') { + + if (isMoveAllPoints) { + point[0] = getPoint(x, prevX, point[0]); + point[1] = getPoint(y, prevY, point[1]); + } + + if (g.pointsToMove === 'stretch-first') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[0], + y: point[1], + width: point[2], + height: point[3], + pointsToMove: g.pointsToMove + }); + + point[0] = newPoints.x; + point[1] = newPoints.y; + point[2] = newPoints.width; + point[3] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-second') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[0], + y: point[1], + width: point[2], + height: point[3], + pointsToMove: g.pointsToMove + }); + + point[1] = newPoints.y; + point[2] = newPoints.width; + point[3] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-third') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[0], + y: point[1], + width: point[2], + height: point[3], + pointsToMove: g.pointsToMove + }); + + point[0] = newPoints.x; + point[2] = newPoints.width; + point[3] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-last') { + point[2] = getPoint(x, prevX, point[2]); + point[3] = getPoint(y, prevY, point[3]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'image') { + + if (isMoveAllPoints) { + point[1] = getPoint(x, prevX, point[1]); + point[2] = getPoint(y, prevY, point[2]); + } + + if (g.pointsToMove === 'stretch-first') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[1], + y: point[2], + width: point[3], + height: point[4], + pointsToMove: g.pointsToMove + }); + + point[1] = newPoints.x; + point[2] = newPoints.y; + point[3] = newPoints.width; + point[4] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-second') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[1], + y: point[2], + width: point[3], + height: point[4], + pointsToMove: g.pointsToMove + }); + + point[2] = newPoints.y; + point[3] = newPoints.width; + point[4] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-third') { + var newPoints = getXYWidthHeight(x, y, prevX, prevY, { + x: point[1], + y: point[2], + width: point[3], + height: point[4], + pointsToMove: g.pointsToMove + }); + + point[1] = newPoints.x; + point[3] = newPoints.width; + point[4] = newPoints.height; + } + + if (g.pointsToMove === 'stretch-last') { + point[3] = getPoint(x, prevX, point[3]); + point[4] = getPoint(y, prevY, point[4]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'quadratic') { + + if (g.pointsToMove === 'starting-points' || isMoveAllPoints) { + point[0] = getPoint(x, prevX, point[0]); + point[1] = getPoint(y, prevY, point[1]); + } + + if (g.pointsToMove === 'control-points' || isMoveAllPoints) { + point[2] = getPoint(x, prevX, point[2]); + point[3] = getPoint(y, prevY, point[3]); + } + + if (g.pointsToMove === 'ending-points' || isMoveAllPoints) { + point[4] = getPoint(x, prevX, point[4]); + point[5] = getPoint(y, prevY, point[5]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + + if (p[0] === 'bezier') { + + if (g.pointsToMove === 'starting-points' || isMoveAllPoints) { + point[0] = getPoint(x, prevX, point[0]); + point[1] = getPoint(y, prevY, point[1]); + } + + if (g.pointsToMove === '1st-control-points' || isMoveAllPoints) { + point[2] = getPoint(x, prevX, point[2]); + point[3] = getPoint(y, prevY, point[3]); + } + + if (g.pointsToMove === '2nd-control-points' || isMoveAllPoints) { + point[4] = getPoint(x, prevX, point[4]); + point[5] = getPoint(y, prevY, point[5]); + } + + if (g.pointsToMove === 'ending-points' || isMoveAllPoints) { + point[6] = getPoint(x, prevX, point[6]); + point[7] = getPoint(y, prevY, point[7]); + } + + points[points.length - 1] = [p[0], point, p[2]]; + } + } + + // ------------------------------------------------------------- + + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var pencilHandler = { + + // ------------------------------------------------------------- + + ismousedown: false, + prevX: 0, + prevY: 0, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + t.prevX = x; + t.prevY = y; + + t.ismousedown = true; + + // make sure that pencil is drawing shapes even + // if mouse is down but mouse isn't moving + tempContext.lineCap = 'round'; + drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); + + points[points.length] = ['line', [t.prevX, t.prevY, x, y], drawHelper.getOptions()]; + + t.prevX = x; + t.prevY = y; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + this.ismousedown = false; + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + if (t.ismousedown) { + tempContext.lineCap = 'round'; + drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); + + points[points.length] = ['line', [t.prevX, t.prevY, x, y], drawHelper.getOptions()]; + + t.prevX = x; + t.prevY = y; + } + } + + // ------------------------------------------------------------- + + }; + + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var eraserHandler = { + + // ------------------------------------------------------------- + + ismousedown: false, + prevX: 0, + prevY: 0, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + t.prevX = x; + t.prevY = y; + + t.ismousedown = true; + + // make sure that pencil is drawing shapes even + // if mouse is down but mouse isn't moving + tempContext.lineCap = 'round'; + drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); + + points[points.length] = ['line', [t.prevX, t.prevY, x, y], drawHelper.getOptions()]; + + t.prevX = x; + t.prevY = y; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + this.ismousedown = false; + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + if (t.ismousedown) { + tempContext.lineCap = 'round'; + drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); + + points[points.length] = ['line', [t.prevX, t.prevY, x, y], drawHelper.getOptions()]; + + t.prevX = x; + t.prevY = y; + } + } + + // ------------------------------------------------------------- + + }; + + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + var textInput = document.getElementById('text-input'); + textInput.onkeyup = function(e) { + if (e.keyCode != 13) return; + + // ENTER key goes to new line + fillText(); + + textHandler.isTextPending = true; + + textHandler.y += 20; + textHandler.pageY += 20; + + textInput.style.top = (textHandler.pageY - 10) + 'px'; + textInput.style.left = (textHandler.pageX - 10) + 'px'; + textInput.style.color = fillStyle == 'transparent' ? 'Black' : fillStyle; + + setTimeout(function() { + textInput.focus(); + }, 200); + }; + + textInput.onblur = function(e) { + if (textInput.value.length) { + fillText(); + return; + } + //textInput.style.top = '-100000px'; + //textInput.style.left = '-100000px'; + //textHandler.isTextPending = false; + }; + + function fillText() { + if (!textHandler.isTextPending) return; + textHandler.isTextPending = false; + + var oldFillStyle = fillStyle; + var oldFont = font; + + fillStyle = 'Black'; + font = '15px Verdana'; + + points[points.length] = ['text', ['"' + textInput.value + '"', textHandler.x, textHandler.y], drawHelper.getOptions()]; + + fillStyle = oldFillStyle; + font = oldFont; + + textInput.style.top = '-100000px'; + textInput.style.left = '-100000px'; + textInput.value = ''; + + drawHelper.redraw(); + } + + var textHandler = { + isTextPending: false, + writeText: function(keyPressed, isBackKeyPressed) { + if(isBackKeyPressed) { + var nativeMeasureText = tempContext.measureText(textHandler.lastKeyPress); + var measuredText = {}; + measuredText.width = parseInt(nativeMeasureText.width); + measuredText.height = parseInt(22 * 1.5); + + textHandler.x -= measuredText.width + 1; + tempContext.fillStyle = 'white'; + + tempContext.fillText(textHandler.lastKeyPress, this.x, this.y - 25); + tempContext.fillRect(this.x, this.y - 25, measuredText.width + 3, measuredText.height); + return; + } + tempContext.fillStyle = 'red'; + tempContext.font = '22px Verdana'; + tempContext.fillText(keyPressed, this.x, this.y); + textHandler.x += parseInt(tempContext.measureText(keyPressed).width); + textHandler.lastKeyPress = keyPressed; + }, + mousedown: function(e) { + textHandler.pageX = e.pageX; + textHandler.pageY = e.pageY; + + textHandler.x = e.pageX - canvas.offsetLeft - 5; + textHandler.y = e.pageY - canvas.offsetTop + 10; + }, + mouseup: function(e) {}, + mousemove: function(e) {} + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var arcHandler = { + + // ------------------------------------------------------------- + + global: { + ismousedown: false, + prevX: 0, + prevY: 0, + prevRadius: 0, + isCircleDrawn: false, + isCircledEnded: true, + isClockwise: false, + arcRangeContainer: null, + arcRange: null + }, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var g = this.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + g.prevX = x; + g.prevY = y; + + g.ismousedown = true; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + var g = this.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + if (g.ismousedown) { + if (!g.isCircleDrawn && g.isCircledEnded) { + var prevX = g.prevX, + prevY = g.prevY, + radius = ((x - prevX) + (y - prevY)) / 3; + + g.prevRadius = radius; + g.isCircleDrawn = true; + g.isCircleEnded = false; + + var c = (2 * Math.PI * radius) / 21, + angle, + xx = prevX > x ? prevX - x : x - prevX, + yy = prevY > y ? prevY - y : y - prevY; + + angle = (xx + yy) / (2 * c); + points[points.length] = ['arc', [prevX + radius, prevY + radius, radius, angle, 1], drawHelper.getOptions()]; + + var arcRange = g.arcRange, + arcRangeContainer = g.arcRangeContainer; + + arcRangeContainer.style.display = 'block'; + arcRange.focus(); + + arcRangeContainer.style.top = (y + canvas.offsetTop + 20) + 'px'; + arcRangeContainer.style.left = x + 'px'; + + arcRange.value = 2; + } else if (g.isCircleDrawn && !g.isCircleEnded) { + this.end(); + } + } + + g.ismousedown = false; + + this.fixAllPoints(); + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var g = this.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var ismousedown = g.ismousedown, + isCircleDrawn = g.isCircleDrawn, + isCircleEnded = g.isCircledEnded; + + if (ismousedown) { + if (!isCircleDrawn && isCircleEnded) { + var prevX = g.prevX, + prevY = g.prevY, + radius = ((x - prevX) + (y - prevY)) / 3; + + tempContext.clearRect(0, 0, 2000, 2000); + + drawHelper.arc(tempContext, [prevX + radius, prevY + radius, radius, Math.PI * 2, true]); + } + } + }, + + // ------------------------------------------------------------- + + fixAllPoints: function() { + var toFixed = this.toFixed; + + for (var i = 0; i < points.length; i++) { + var p = points[i], + point; + if (p[0] === 'arc') { + point = p[1]; + points[i] = ['arc', [toFixed(point[0]), toFixed(point[1]), toFixed(point[2]), toFixed(point[3]), point[4]], + p[2] + ]; + } + } + }, + + // ------------------------------------------------------------- + + init: function() { + var markIsClockwise = find('is-clockwise'), + g = this.global; + + g.arcRangeContainer = find('arc-range-container'); + g.arcRange = find('arc-range'); + + addEvent(markIsClockwise, 'change', function(e) { + g.isClockwise = markIsClockwise.checked; + + g.arcRange.value = arcHandler.toFixed(g.arcRange.value); + g.arcRange.focus(); + + arcHandler.arcRangeHandler(e); + + if (!points.length) return; + + var p = points[points.length - 1], + point = p[1]; + + tempContext.clearRect(0, 0, innerWidth, innerHeight); + drawHelper.arc(tempContext, [point[0], point[1], point[2], point[3], point[4]]); + }); + + var arcRange = g.arcRange; + addEvent(arcRange, 'keydown', this.arcRangeHandler); + addEvent(arcRange, 'focus', this.arcRangeHandler); + }, + + // ------------------------------------------------------------- + + arcRangeHandler: function(e) { + var g = arcHandler.global, + arcRange = g.arcRange; + + var key = e.keyCode, + value = +arcRange.value; + if (key == 39 || key == 40) arcRange.value = (value < 2 ? value : 1.98) + .02; + if (key == 37 || key == 38) arcRange.value = (value > 0 ? value : .02) - .02; + + if (!key || key == 13 || key == 39 || key == 40 || key == 37 || key == 38) { + var range = Math.PI * arcHandler.toFixed(value); + var p = points[points.length - 1]; + + if (p[0] === 'arc') { + var point = p[1]; + points[points.length - 1] = ['arc', [point[0], point[1], point[2], range, g.isClockwise ? 1 : 0], + p[2] + ]; + + drawHelper.redraw(); + } + } + }, + + // ------------------------------------------------------------- + + toFixed: function(input) { + return Number(input).toFixed(1); + }, + + // ------------------------------------------------------------- + + end: function() { + var g = this.global; + + g.arcRangeContainer.style.display = 'none'; + g.arcRange.value = 2; + + g.isCircleDrawn = false; + g.isCircleEnded = true; + + drawHelper.redraw(); + } + + // ------------------------------------------------------------- + }; + + arcHandler.init(); + + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var lineHandler = { + + // ------------------------------------------------------------- + + ismousedown: false, + prevX: 0, + prevY: 0, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + t.prevX = x; + t.prevY = y; + + t.ismousedown = true; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + if (t.ismousedown) { + points[points.length] = ['line', [t.prevX, t.prevY, x, y], drawHelper.getOptions()]; + + t.ismousedown = false; + } + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + if (t.ismousedown) { + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + drawHelper.line(tempContext, [t.prevX, t.prevY, x, y]); + } + } + + // ------------------------------------------------------------- + + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + var rectHandler = { + + // ------------------------------------------------------------- + + ismousedown: false, + prevX: 0, + prevY: 0, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + t.prevX = x; + t.prevY = y; + + t.ismousedown = true; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + if (t.ismousedown) { + points[points.length] = ['rect', [t.prevX, t.prevY, x - t.prevX, y - t.prevY], drawHelper.getOptions()]; + + t.ismousedown = false; + } + + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + if (t.ismousedown) { + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + drawHelper.rect(tempContext, [t.prevX, t.prevY, x - t.prevX, y - t.prevY]); + } + } + + // ------------------------------------------------------------- + + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var quadraticHandler = { + + // ------------------------------------------------------------- + + global: { + ismousedown: false, + prevX: 0, + prevY: 0, + controlPointX: 0, + controlPointY: 0, + isFirstStep: true, + isLastStep: false + }, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var g = this.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + if (!g.isLastStep) { + g.prevX = x; + g.prevY = y; + } + + g.ismousedown = true; + + if (g.isLastStep && g.ismousedown) { + this.end(x, y); + } + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + var g = this.global; + + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + if (g.ismousedown && g.isFirstStep) { + g.controlPointX = x; + g.controlPointY = y; + + g.isFirstStep = false; + g.isLastStep = true; + } + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var g = this.global; + + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + if (g.ismousedown && g.isFirstStep) { + drawHelper.quadratic(tempContext, [g.prevX, g.prevY, x, y, x, y]); + } + + if (g.isLastStep) { + drawHelper.quadratic(tempContext, [g.prevX, g.prevY, g.controlPointX, g.controlPointY, x, y]); + } + }, + + // ------------------------------------------------------------- + + end: function(x, y) { + var g = this.global; + + if (!g.ismousedown) return; + + g.isLastStep = false; + + g.isFirstStep = true; + g.ismousedown = false; + + x = x || g.controlPointX || g.prevX; + y = y || g.controlPointY || g.prevY; + + points[points.length] = ['quadratic', [g.prevX, g.prevY, g.controlPointX, g.controlPointY, x, y], drawHelper.getOptions()]; + } + + // ------------------------------------------------------------- + + }; + + // ------------------------------------------------------------- + + var FileSelector = function() { + var selector = this; + + selector.selectSingleFile = selectFile; + selector.selectMultipleFiles = function(callback) { + selectFile(callback, true); + }; + + function selectFile(callback, multiple) { + var file = document.createElement('input'); + file.type = 'file'; + + if (multiple) { + file.multiple = true; + } + + file.onchange = function() { + if (multiple) { + if (!file.files.length) { + console.error('No file selected.'); + return; + } + callback(file.files); + return; + } + + if (!file.files[0]) { + console.error('No file selected.'); + return; + } + + callback(file.files[0]); + + file.parentNode.removeChild(file); + }; + file.style.display = 'none'; + (document.body || document.documentElement).appendChild(file); + fireClickEvent(file); + } + + function fireClickEvent(element) { + var evt = new window.MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + button: 0, + buttons: 0, + mozInputSource: 1 + }); + + var fired = element.dispatchEvent(evt); + } + }; + + // ------------------------------------------------------------- + var imageHandler = { + + // ------------------------------------------------------------- + + lastImageURL: null, + lastImageIndex: 0, + images: [], + + ismousedown: false, + prevX: 0, + prevY: 0, + + // ------------------------------------------------------------- + + mousedown: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + + t.prevX = x; + t.prevY = y; + + t.ismousedown = true; + }, + + // ------------------------------------------------------------- + + mouseup: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + if (t.ismousedown) { + points[points.length] = ['image', [imageHandler.lastImageURL, t.prevX, t.prevY, x - t.prevX, y - t.prevY, imageHandler.lastImageIndex], drawHelper.getOptions()]; + + t.ismousedown = false; + } + + }, + + // ------------------------------------------------------------- + + mousemove: function(e) { + var x = e.pageX - canvas.offsetLeft, + y = e.pageY - canvas.offsetTop; + + var t = this; + if (t.ismousedown) { + tempContext.clearRect(0, 0, innerWidth, innerHeight); + + drawHelper.image(tempContext, [imageHandler.lastImageURL, t.prevX, t.prevY, x - t.prevX, y - t.prevY, imageHandler.lastImageIndex]); + } + } + + // ------------------------------------------------------------- + + }; + // ------------------------------------------------------------- + + // ------------------------------------------------------------- + + var canvas = tempContext.canvas, + isTouch = 'createTouch' in document; + + // ------------------------------------------------------------- + + addEvent(canvas, isTouch ? 'touchstart' : 'mousedown', function(e) { + if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { + pageX: 0, + pageY: 0 + }; + + var cache = is; + + if (cache.isLine) lineHandler.mousedown(e); + else if (cache.isArc) arcHandler.mousedown(e); + else if (cache.isRectangle) rectHandler.mousedown(e); + else if (cache.isQuadraticCurve) quadraticHandler.mousedown(e); + else if (cache.isBezierCurve) bezierHandler.mousedown(e); + else if (cache.isDragLastPath || cache.isDragAllPaths) dragHelper.mousedown(e); + else if (cache.isPencil) pencilHandler.mousedown(e); + else if (cache.isEraser) eraserHandler.mousedown(e); + else if (cache.isText) textHandler.mousedown(e); + else if (cache.isImage) imageHandler.mousedown(e); + + // drawHelper.redraw(); + }); + + // ------------------------------------------------------------- + + addEvent(canvas, isTouch ? 'touchend' : 'mouseup', function(e) { + if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { + pageX: 0, + pageY: 0 + }; + + var cache = is; + + if (cache.isLine) lineHandler.mouseup(e); + else if (cache.isArc) arcHandler.mouseup(e); + else if (cache.isRectangle) rectHandler.mouseup(e); + else if (cache.isQuadraticCurve) quadraticHandler.mouseup(e); + else if (cache.isBezierCurve) bezierHandler.mouseup(e); + else if (cache.isDragLastPath || cache.isDragAllPaths) dragHelper.mouseup(e); + else if (cache.isPencil) pencilHandler.mouseup(e); + else if (cache.isEraser) eraserHandler.mouseup(e); + else if (cache.isText) textHandler.mouseup(e); + else if (cache.isImage) imageHandler.mouseup(e); + + // drawHelper.redraw(); + }); + + // ------------------------------------------------------------- + + addEvent(canvas, isTouch ? 'touchmove' : 'mousemove', function(e) { + if (isTouch) e = e.pageX ? e : e.touches.length ? e.touches[0] : { + pageX: 0, + pageY: 0 + }; + + var cache = is; + + if (cache.isLine) lineHandler.mousemove(e); + else if (cache.isArc) arcHandler.mousemove(e); + else if (cache.isRectangle) rectHandler.mousemove(e); + else if (cache.isQuadraticCurve) quadraticHandler.mousemove(e); + else if (cache.isBezierCurve) bezierHandler.mousemove(e); + else if (cache.isDragLastPath || cache.isDragAllPaths) dragHelper.mousemove(e); + else if (cache.isPencil) pencilHandler.mousemove(e); + else if (cache.isEraser) eraserHandler.mousemove(e); + else if (cache.isText) textHandler.mousemove(e); + else if (cache.isImage) imageHandler.mousemove(e); + + // drawHelper.redraw(); + }); + + // ------------------------------------------------------------- + + var keyCode; + + // ------------------------------------------------------------- + + function onkeydown(e) { + keyCode = e.keyCode; + + if (e.metaKey) { + isControlKeyPressed = true; + keyCode = 17; + } + + if (!isControlKeyPressed && keyCode === 17) { + isControlKeyPressed = true; + } + } + + addEvent(document, 'keydown', onkeydown); + + // ------------------------------------------------------------- + + function onkeyup(e) { + if (e.which == null && (e.charCode != null || e.keyCode != null)) { + e.which = e.charCode != null ? e.charCode : e.keyCode; + } + + keyCode = e.which || e.keyCode || 0; + + if(keyCode == 8 || keyCode == 46) { + textHandler.writeText(textHandler.lastKeyPress, true); + } + + /*-------------------------- Ctrl + Z --------------------------*/ + + if (isControlKeyPressed && keyCode === 90) { + if (points.length) { + points.length = points.length - 1; + drawHelper.redraw(); + } + } + + /*-------------------------- Ctrl + A --------------------------*/ + + if (isControlKeyPressed && keyCode === 65) { + dragHelper.global.startingIndex = 0; + + endLastPath(); + + setSelection(find('drag-all-paths'), 'DragAllPaths'); + } + + /*-------------------------- Ctrl + C --------------------------*/ + + if (isControlKeyPressed && keyCode === 67 && points.length) { + copy(); + } + + /*-------------------------- Ctrl + V --------------------------*/ + if (isControlKeyPressed && keyCode === 86 && copiedStuff.length) { + paste(); + } + + /*-------------------------- Ending the Control Key --------------------------*/ + + if (typeof e.metaKey !== 'undefined' && e.metaKey === false) { + isControlKeyPressed = false; + keyCode = 17; + } + + if (keyCode === 17) { + isControlKeyPressed = false; + } + } + + addEvent(document, 'keyup', onkeyup); + + + function onkeypress(e) { + if (e.which == null && (e.charCode != null || e.keyCode != null)) { + e.which = e.charCode != null ? e.charCode : e.keyCode; + } + + keyCode = e.which || e.keyCode || 0; + + var inp = String.fromCharCode(keyCode); + if (/[a-zA-Z0-9-_ !?|\/'",.=:;(){}\[\]`~@#$%^&*+-]/.test(inp)) { + textHandler.writeText(String.fromCharCode(keyCode)); + } + } + + addEvent(document, 'keypress', onkeypress); + + points[points.length] = ['rect', [0, 0, context.canvas.width, context.canvas.height, drawHelper.getOptions()]]; +})(); + +window.location.hash="no-back-button"; +window.location.hash="Again-No-back-button";//again because google chrome don't insert first hash into history +window.onhashchange=function(){window.location.hash="no-back-button";} diff --git a/RecordRTC/Canvas-Recording/index.html b/RecordRTC/Canvas-Recording/index.html index 87d6b0ab..667a9b73 100644 --- a/RecordRTC/Canvas-Recording/index.html +++ b/RecordRTC/Canvas-Recording/index.html @@ -5,6 +5,11 @@ + + + + +

Welcome to RecordRTC Canvas/HTML Recorder!

-

Content is edit-able.

+
+ You can resize image or above header and drag/move video:
+ +
- -
+ +
There is another demo, that allows you record drawings!
+ +
 var recorder = RecordRTC(document.documentElement, {
-    type: 'canvas'
+    type: 'canvas',
+    showMousePointer: true
 });
 recorder.startRecording();
 recorder.stopRecording(function(url) {
@@ -109,33 +127,36 @@ 

Content is edit-able.

- - - - + + + + + -←WebRTC Experiments Homepage \ No newline at end of file +←WebRTC Experiments Homepage diff --git a/RecordRTC/Canvas-Recording/record-canvas-drawings.html b/RecordRTC/Canvas-Recording/record-canvas-drawings.html new file mode 100644 index 00000000..12764a9a --- /dev/null +++ b/RecordRTC/Canvas-Recording/record-canvas-drawings.html @@ -0,0 +1,629 @@ + + + + + + + + RecordRTC and Canvas Designer ® Muaz Khan + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
Use arrow keys ↑↓
+
+ + + +
+ +
+ + + + + +
+
+ + +
+
+ + +
+
+ + + +
+ + + +
Done
+
+ + + +
+
+ + +
+ +
+ + +
+ +
Done
+
+ + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
Done
+
+ + + + + + + +
+ + + + + + + + + +
+ + Fps: + + +
+ + + + \ No newline at end of file diff --git a/RecordRTC/Gruntfile.js b/RecordRTC/Gruntfile.js index f70215fb..fc3f458b 100644 --- a/RecordRTC/Gruntfile.js +++ b/RecordRTC/Gruntfile.js @@ -7,6 +7,8 @@ module.exports = function(grunt) { scope: 'devDependencies' }); + var banner = '\'use strict\';\n\n// Last time updated: <%= grunt.template.today("UTC:yyyy-mm-dd h:MM:ss TT Z") %>\n\n'; + // configure project grunt.initConfig({ // make node configurations available @@ -15,7 +17,7 @@ module.exports = function(grunt) { options: { stripBanners: true, separator: '\n', - banner: '// Last time updated at <%= grunt.template.today("dddd, mmmm dS, yyyy, h:MM:ss TT") %> \n\n' + banner: banner }, dist: { src: [ @@ -26,6 +28,7 @@ module.exports = function(grunt) { 'dev/MRecordRTC.js', 'dev/Cross-Browser-Declarations.js', 'dev/Storage.js', + 'dev/isMediaRecorderCompatible.js', 'dev/MediaStreamRecorder.js', 'dev/StereoAudioRecorder.js', 'dev/CanvasRecorder.js', @@ -37,25 +40,8 @@ module.exports = function(grunt) { dest: 'RecordRTC.js', }, }, - htmlhint: { - html1: { - src: [ - './Canvas-Recording/*.html', - './MRecordRTC/*.html', - './PHP-and-FFmpeg/*.html', - './RecordRTC-over-Socketio/*.html', - './RecordRTC-to-Nodejs/static/*.html', - './RecordRTC-to-PHP/*.html', - './*.html' - ], - options: { - 'tag-pair': true - } - } - }, jshint: { options: { - globals: { webkitIndexedDB: true, mozIndexedDB: true, @@ -109,16 +95,12 @@ module.exports = function(grunt) { typed: true, worker: true }, - files: ['RecordRTC.js'], - ignore_warning: { - options: { - '-W015': true - } - } + files: ['RecordRTC.js'] }, uglify: { options: { - mangle: false + mangle: false, + banner: banner }, my_target: { files: { @@ -189,5 +171,5 @@ module.exports = function(grunt) { // set default tasks to run when grunt is called without parameters // http://gruntjs.com/api/grunt.task - grunt.registerTask('default', ['concat', 'jsbeautifier', 'htmlhint', 'jshint', 'uglify']); + grunt.registerTask('default', ['concat', 'jsbeautifier', 'jshint', 'uglify']); }; diff --git a/RecordRTC/MRecordRTC/README.md b/RecordRTC/MRecordRTC/README.md index b2c98d0c..76d6cb0a 100644 --- a/RecordRTC/MRecordRTC/README.md +++ b/RecordRTC/MRecordRTC/README.md @@ -1,5 +1,9 @@ ## [MRecordRTC](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/MRecordRTC) i.e. Multi-RecordRTC! / [Demo](https://www.webrtc-experiment.com/RecordRTC/MRecordRTC/) +[RecordRTC Documentation](http://RecordRTC.org/) / [RecordRTC Wiki Pages](https://github.com/muaz-khan/RecordRTC/wiki) / [RecordRTC Demo](https://www.webrtc-experiment.com/RecordRTC/) / [WebRTC Experiments](https://www.webrtc-experiment.com/) + +[![npm](https://img.shields.io/npm/v/recordrtc.svg)](https://npmjs.org/package/recordrtc) [![downloads](https://img.shields.io/npm/dm/recordrtc.svg)](https://npmjs.org/package/recordrtc) [![Build Status: Linux](https://travis-ci.org/muaz-khan/RecordRTC.png?branch=master)](https://travis-ci.org/muaz-khan/RecordRTC) + This [WebRTC](https://www.webrtc-experiment.com/) experiment is using [RecordRTC.js](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC) to record multiple audio/video/gif streams. 1. It simplifies coding for multi-streams recording i.e. audio+video recording @@ -10,14 +14,20 @@ This [WebRTC](https://www.webrtc-experiment.com/) experiment is using [RecordRTC = ```html - + - - - + + + - + @@ -153,17 +150,17 @@

var mRecordRTC = new MRecordRTC(); mRecordRTC.mediaType = { - audio: true, - video: true + audio: true, // or StereoAudioRecorder or MediaStreamRecorder + video: true // or WhammyRecorder or MediaStreamRecorder }; if (webrtcDetectedBrowser === 'edge') { // Microsoft Edge currently supports only audio and gif recording mRecordRTC.mediaType = { - audio: true + audio: StereoAudioRecorder }; } - //mRecordRTC.bufferSize = 16384; + // mRecordRTC.bufferSize = 16384; document.querySelector('#start').onclick = function() { this.disabled = true; @@ -197,7 +194,7 @@

mRecordRTC.addStream(stream); mRecordRTC.startRecording(); - }, function(errro) { + }, function(error) { alert(JSON.stringify(error)); }); }; @@ -302,13 +299,58 @@

Using MRecordRTC...

How to use MRecordRTC?

-                <script src="//cdn.webrtc-experiment.com/RecordRTC.js"></script>
-            
+<script src="https://cdn.webrtc-experiment.com/RecordRTC.js"></script> +
-                var recorder = new MRecordRTC(); recorder.addStream(MediaStream); recorder.mediaType = { audio: true, video: true, gif: true }; recorder.startRecording(); recorder.stopRecording(function(url, type) { document.querySelector(type).src = url; }); recorder.getBlob(function(blobs) { blobs.audio --- audio blob blobs.video --- video blob blobs.gif --- gif blob }); // or var blobs = recorder.getBlob(); var audioBlob = blobs.audio; var videoBlob = blobs.video; var gifBlob = blobs.gif; // invoke save-as dialog // for all recorded blobs recorder.save(); recorder.writeToDisk(); // get all blobs from disk MRecordRTC.getFromDisk('all', function(dataURL, type) { type == 'audio' type == 'video' type == 'gif' }); // or get just single blob MRecordRTC.getFromDisk('audio', function(dataURL) { // only audio blob is returned from disk! });
-            
+var recorder = new MRecordRTC(); +recorder.addStream(MediaStream); +recorder.mediaType = { + audio: true, + video: true, + gif: true +}; +// mimeType is optional and should be set only in advance cases. +recorder.mimeType = { + audio: 'audio/wav', + video: 'video/webm', + gif: 'image/gif' +}; +recorder.startRecording(); +recorder.stopRecording(function(url, type) { + document.querySelector(type).src = url; +}); + +recorder.getBlob(function(blobs) { + blobs.audio --- audio blob + blobs.video --- video blob + blobs.gif --- gif blob +}); +// or +var blobs = recorder.getBlob(); +var audioBlob = blobs.audio; +var videoBlob = blobs.video; +var gifBlob = blobs.gif; + +// invoke save-as dialog +// for all recorded blobs +recorder.save(); + +recorder.writeToDisk(); + +// get all blobs from disk +MRecordRTC.getFromDisk('all', function(dataURL, type) { + type == 'audio' + type == 'video' + type == 'gif' +}); + +// or get just single blob +MRecordRTC.getFromDisk('audio', function(dataURL) { + // only audio blob is returned from disk! +}); +
@@ -318,6 +360,12 @@

+
+

RecordRTC Issues +

+
+
+

Feedback

@@ -328,7 +376,7 @@

Feedback

-

Latest Updates +

Latest Updates

@@ -344,8 +392,10 @@

- + \ No newline at end of file diff --git a/RecordRTC/PHP-and-FFmpeg/save.php b/RecordRTC/PHP-and-FFmpeg/save.php index 5cbbe092..7aa60450 100644 --- a/RecordRTC/PHP-and-FFmpeg/save.php +++ b/RecordRTC/PHP-and-FFmpeg/save.php @@ -32,7 +32,7 @@ foreach($OSList as $CurrOS=>$Match) { // Find a match - if (eregi($Match, $_SERVER['HTTP_USER_AGENT'])) + if (preg_match("/".$Match."/i", $_SERVER['HTTP_USER_AGENT'])) { // We found the correct match break; diff --git a/RecordRTC/README.md b/RecordRTC/README.md index 8692d91f..f75ffe0b 100644 --- a/RecordRTC/README.md +++ b/RecordRTC/README.md @@ -24,13 +24,13 @@ Please check [dev](https://github.com/muaz-khan/RecordRTC/tree/master/dev) direc ## Browsers Support: -| Browser | Support | -| ------------- |-------------| -| Firefox | [Stable](http://www.mozilla.org/en-US/firefox/new/) / [Aurora](http://www.mozilla.org/en-US/firefox/aurora/) / [Nightly](http://nightly.mozilla.org/) | -| Google Chrome | [Stable](https://www.google.com/intl/en_uk/chrome/browser/) / [Canary](https://www.google.com/intl/en/chrome/browser/canary.html) / [Beta](https://www.google.com/intl/en/chrome/browser/beta.html) / [Dev](https://www.google.com/intl/en/chrome/browser/index.html?extra=devchannel#eula) | -| Opera | [Stable](http://www.opera.com/) / [NEXT](http://www.opera.com/computer/next) | -| Android | [Chrome](https://play.google.com/store/apps/details?id=com.chrome.beta&hl=en) / [Firefox](https://play.google.com/store/apps/details?id=org.mozilla.firefox) / [Opera](https://play.google.com/store/apps/details?id=com.opera.browser) | -| Microsoft Edge | [Normal Build](https://www.microsoft.com/en-us/windows/microsoft-edge) | +| Browser | Support | Features | +| ------------- |-------------|-------------| +| Firefox | [Stable](http://www.mozilla.org/en-US/firefox/new/) / [Aurora](http://www.mozilla.org/en-US/firefox/aurora/) / [Nightly](http://nightly.mozilla.org/) | Audio+Video (Both local/remote) | +| Google Chrome | [Stable](https://www.google.com/intl/en_uk/chrome/browser/) / [Canary](https://www.google.com/intl/en/chrome/browser/canary.html) / [Beta](https://www.google.com/intl/en/chrome/browser/beta.html) / [Dev](https://www.google.com/intl/en/chrome/browser/index.html?extra=devchannel#eula) | Audio+Video (Both local/remote) | +| Opera | [Stable](http://www.opera.com/) / [NEXT](http://www.opera.com/computer/next) | Audio/Vidoe Separately | +| Android | [Chrome](https://play.google.com/store/apps/details?id=com.chrome.beta&hl=en) / [Firefox](https://play.google.com/store/apps/details?id=org.mozilla.firefox) / [Opera](https://play.google.com/store/apps/details?id=com.opera.browser) | Audio/Vidoe Separately | +| Microsoft Edge | [Normal Build](https://www.microsoft.com/en-us/windows/microsoft-edge) | Only Audio | ## How RecordRTC encodes wav/webm? @@ -41,18 +41,18 @@ Please check [dev](https://github.com/muaz-khan/RecordRTC/tree/master/dev) direc ## RecordRTC Demos -1. [RecordRTC to Node.js](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/RecordRTC-to-Nodejs) -2. [RecordRTC to PHP](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/RecordRTC-to-PHP) -3. [RecordRTC to ASP.NET MVC](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/RecordRTC-to-ASPNETMVC) -4. [RecordRTC & HTML-2-Canvas i.e. Canvas/HTML Recording!](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/Canvas-Recording) -5. [MRecordRTC i.e. Multi-RecordRTC!](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/MRecordRTC) +1. [RecordRTC to Node.js](https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-Nodejs) +2. [RecordRTC to PHP](https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-PHP) +3. [RecordRTC to ASP.NET MVC](https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-to-ASPNETMVC) +4. [RecordRTC & HTML-2-Canvas i.e. Canvas/HTML Recording!](https://github.com/muaz-khan/RecordRTC/tree/master/Canvas-Recording) +5. [MRecordRTC i.e. Multi-RecordRTC!](https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC) 6. [RecordRTC on Ruby!](https://github.com/cbetta/record-rtc-experiment) -7. [RecordRTC over Socket.io](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/RecordRTC-over-Socketio) +7. [RecordRTC over Socket.io](https://github.com/muaz-khan/RecordRTC/tree/master/RecordRTC-over-Socketio) 8. [ffmpeg-asm.js and RecordRTC! Audio/Video Merging & Transcoding!](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/ffmpeg) -9. [RecordRTC / PHP / FFmpeg](https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RecordRTC/PHP-and-FFmpeg) +9. [RecordRTC / PHP / FFmpeg](https://github.com/muaz-khan/RecordRTC/tree/master/PHP-and-FFmpeg) 10. [Record Audio and upload to Nodejs server](https://www.npmjs.org/package/record-audio) 11. [ConcatenateBlobs.js](https://github.com/muaz-khan/ConcatenateBlobs) - Concatenate multiple recordings in single Blob! -12. [Remote stream recording](https://www.webrtc-experiment.com/demos/remote-stream-recording.html) +12. [Remote audio-stream recording](https://www.webrtc-experiment.com/demos/remote-stream-recording.html) or [a real p2p demo](https://www.webrtc-experiment.com/RTCMultiConnection/RecordRTC-and-RTCMultiConnection.html) 13. [Mp3 or Wav Recording](https://www.webrtc-experiment.com/RecordRTC/Record-Mp3-or-Wav.html) ## How to link? @@ -77,10 +77,21 @@ To use it: - + - + +``` + +It is suggested to link specific release: + +* https://github.com/muaz-khan/RecordRTC/releases + +E.g. + +```html + + ``` There are some other NPM packages regarding RecordRTC: @@ -90,14 +101,14 @@ There are some other NPM packages regarding RecordRTC: ## How to capture stream? ```html - + - + +
+ + + +``` + +Live Demo: + +* https://www.webrtc-experiment.com/RecordRTC/Canvas-Recording/record-canvas-drawings.html + +Watch a video: https://vimeo.com/152119435 + # API Reference ## `initRecorder` @@ -353,6 +395,8 @@ It means that ALL_BROWSERS will be using [StereoAudioRecorder](http://RecordRTC. This feature brings remote audio recording support in Firefox, and local audio recording support in Microsoft Edge. +Note: Chrome `>=50` supports remote audio+video recording. + You can even force `WhammyRecorder` on Firefox however webp format isn't yet supported in standard Firefox builds. It simply means that, you're skipping MediaRecorder API in Firefox. ## `type` @@ -670,51 +714,6 @@ recordRTC.getFromDisk(function(dataURL) { In the above example; you can see that `recordRTC` instance object is used instead of global `RecordRTC` object. -# Clarifications - -## Is WinXP supported? - -No WinXP SP2 based "Chrome" support. However, RecordRTC works on WinXP Service Pack 3. - -## Is Chrome on Android supported? - -RecordRTC uses WebAudio API for stereo-audio recording. AFAIK, WebAudio is not supported on android chrome releases, yet. - -Firefox merely supports audio-recording on Android devices. - -## Stereo or Mono? - -Audio recording fails for `mono` audio. So, try `stereo` audio only. - -MediaRecorder API (in Firefox) seems using mono-audio-recording instead. - -## Possible issues/failures: - -**This section applies only to StereoAudioRecorder:** - -Do you know "RecordRTC" fails recording audio because following conditions fails: - -1. Sample rate and channel configuration must be the same for input and output sides on Windows i.e. audio input/output devices mismatch -2. Only the Default microphone device can be used for capturing. -3. The requesting scheme is none of the following: http, https, chrome, extension's, or file (only works with `--allow-file-access-from-files`) -4. The browser cannot create/initialize the metadata database for the API under the profile directory - -If you see this error message: `Uncaught Error: SecurityError: DOM Exception 18`; it means that you're using `HTTP`; whilst your webpage is loading worker file (i.e. `audio-recorder.js`) from `HTTPS`. Both files's (i.e. `RecordRTC.js` and `audio-recorder.js`) scheme MUST be same! - -## Web Audio APIs requirements - -1. If you're on Windows, you have to be running WinXP SP3, Windows Vista or better (will not work on Windows XP SP2 or earlier). -2. On Windows, audio input hardware must be set to the same sample rate as audio output hardware. -3. On Mac and Windows, the audio input device must be at least stereo (i.e. a mono/single-channel USB microphone WILL NOT work). - -## Why stereo? - -If you explorer chromium code; you'll see that some APIs can only be successfully called for `WAV` files with `stereo` audio. - -Stereo audio is only supported for WAV files. - -RecordRTC is unable to record "mono" audio on chrome; however it seems that we can covert channels from "stereo" to "mono" using WebAudio API, though. MediaRecorder API's encoder only support 48k/16k mono audio channel (on Firefox Nightly). - ## Credits 1. [Recorderjs](https://github.com/mattdiamond/Recorderjs) for audio recording diff --git a/RecordRTC/RecordRTC-to-PHP/index.html b/RecordRTC/RecordRTC-to-PHP/index.html index 36837d58..f5e348c3 100644 --- a/RecordRTC/RecordRTC-to-PHP/index.html +++ b/RecordRTC/RecordRTC-to-PHP/index.html @@ -84,7 +84,7 @@ - + diff --git a/RecordRTC/RecordRTC.js b/RecordRTC/RecordRTC.js index 2a7d9cb0..3553568e 100644 --- a/RecordRTC/RecordRTC.js +++ b/RecordRTC/RecordRTC.js @@ -1,71 +1,13 @@ -// Last time updated at Tuesday, October 27th, 2015, 4:33:33 PM +'use strict'; + +// Last time updated: 2016-03-17 12:41:43 PM UTC -// links: // Open-Sourced: https://github.com/muaz-khan/RecordRTC -// https://cdn.WebRTC-Experiment.com/RecordRTC.js -// https://www.WebRTC-Experiment.com/RecordRTC.js -// npm install recordrtc -// http://recordrtc.org/ -// updates? -/* --. Added support for MediaRecorder API in Chrome. Currently requires: RecordRTC(stream, {recorderType: MediaStreamRecorder}) --. mimeType, bitsPerSecond, audioBitsPerSecond, videoBitsPerSecond added. --. CanvasRecorder.js updated to support Firefox. (experimental) --. Now you can reuse single RecordRTC object i.e. stop/start/stop/start/ and so on. --. GifRecorder.js can now record HTMLCanvasElement|CanvasRenderingContext2D as well. --. added: frameInterval:20 for WhammyRecorder.js --. chrome issue audio plus 720p-video recording can be fixed by setting bufferSize:16384 --. fixed Firefox save-as dialog i.e. recordRTC.save('filen-name') --. "indexedDB" bug fixed for Firefox. --. numberOfAudioChannels:1 can be passed to reduce WAV size in Chrome. --. StereoRecorder.js is removed. It was redundant. Now RecordRTC is directly using: StereoAudioRecorder.js --. mergeProps is removed. It was redundant. --. reformatProps is removed. Now plz pass exact frameRate/sampleRate instead of frame-rate/sample-rate --. Firefox supports remote-audio-recording since v28 - RecordRTC(remoteStream, { recorderType: StereoAudioRecorder }); --. added 3 methods: initRecorder, setRecordingDuration and clearRecordedData --. Microsoft Edge support added (only-audio-yet). --. You can pass "recorderType" - RecordRTC(stream, { recorderType: StereoAudioRecorder }); --. If MediaStream is suddenly stopped in Firefox. --. Added "disableLogs" - RecordRTC(stream, { disableLogs: true }); --. You can pass "bufferSize:0" - RecordRTC(stream, { bufferSize: 0 }); --. You can set "leftChannel" - RecordRTC(stream, { leftChannel: true }); --. Added functionality for analyse black frames and cut them - pull#293 --. if you're recording GIF, you must link: https://cdn.webrtc-experiment.com/gif-recorder.js --. You can set "frameInterval" for video - RecordRTC(stream, { type: 'video', frameInterval: 100 }); -*/ - -//------------------------------------ - -// Browsers Support:: -// Chrome (all versions) [ audio/video separately ] -// Firefox ( >= 29 ) [ audio/video in single webm/mp4 container or only audio in ogg ] -// Opera (all versions) [ same as chrome ] -// Android (Chrome) [ only video ] -// Android (Opera) [ only video ] -// Android (Firefox) [ only video ] -// Microsoft Edge (Only Audio & Gif) - -//------------------------------------ +//-------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence -//------------------------------------ -// Note: RecordRTC.js is using 3 other libraries; you need to accept their licences as well. -//------------------------------------ -// 1. RecordRTC.js -// 2. MRecordRTC.js -// 3. Cross-Browser-Declarations.js -// 4. Storage.js -// 5. MediaStreamRecorder.js -// 6. StereoAudioRecorder.js -// 7. CanvasRecorder.js -// 8. WhammyRecorder.js -// 9. Whammy.js -// 10. DiskStorage.js -// 11. GifRecorder.js -//------------------------------------ - -'use strict'; +//-------------------------------------------------- // ____________ // RecordRTC.js @@ -125,10 +67,6 @@ function RecordRTC(mediaStream, config) { } function initRecorder(initCallback) { - if (!config.disableLogs) { - console.debug('initializing ' + config.type + ' stream recorder.'); - } - if (initCallback) { config.initCallback = function() { initCallback(); @@ -140,6 +78,10 @@ function RecordRTC(mediaStream, config) { mediaRecorder = new Recorder(mediaStream, config); mediaRecorder.record(); + + if (!config.disableLogs) { + console.debug('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type); + } } function stopRecording(callback) { @@ -219,6 +161,10 @@ function RecordRTC(mediaStream, config) { } } + function readFile(_blob) { + postMessage(new FileReaderSync().readAsDataURL(_blob)); + } + function getDataURL(callback, _mediaRecorder) { if (!callback) { throw 'Pass a callback function over getDataURL.'; @@ -237,10 +183,8 @@ function RecordRTC(mediaStream, config) { return; } - if (typeof Worker !== 'undefined') { - var webWorker = processInWebWorker(function readFile(_blob) { - postMessage(new FileReaderSync().readAsDataURL(_blob)); - }); + if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) { + var webWorker = processInWebWorker(readFile); webWorker.onmessage = function(event) { callback(event.data); @@ -705,7 +649,7 @@ if (typeof define === 'function' && define.amd) { * var options = RecordRTCConfiguration(mediaStream, options); * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.} + * @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, getNativeBlob:true, etc.} */ function RecordRTCConfiguration(mediaStream, config) { @@ -739,7 +683,7 @@ function RecordRTCConfiguration(mediaStream, config) { } if (!config.bitsPerSecond) { - config.bitsPerSecond = 128000; + // config.bitsPerSecond = 128000; } } @@ -805,12 +749,9 @@ function GetRecorderType(mediaStream, config) { } // todo: enable below block when MediaRecorder in Chrome gets out of flags; and it also supports audio recording. - if (false && isChrome && recorder === WhammyRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { + if (isMediaRecorderCompatible() && isChrome && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { if (mediaStream.getVideoTracks().length) { recorder = MediaStreamRecorder; - if (!config.disableLogs) { - console.debug('Using MediaRecorder API in chrome!'); - } } } @@ -818,6 +759,10 @@ function GetRecorderType(mediaStream, config) { recorder = config.recorderType; } + if (!config.disableLogs && !!recorder && !!recorder.name) { + console.debug('Using recorderType:', recorder.name || recorder.constructor.name); + } + return recorder; } @@ -835,9 +780,15 @@ function GetRecorderType(mediaStream, config) { * var recorder = new MRecordRTC(); * recorder.addStream(MediaStream); * recorder.mediaType = { - * audio: true, - * video: true, - * gif: true + * audio: true, // or StereoAudioRecorder or MediaStreamRecorder + * video: true, // or WhammyRecorder or MediaStreamRecorder + * gif: true // or GifRecorder + * }; + * // mimeType is optional and should be set only in advance cases. + * recorder.mimeType = { + * audio: 'audio/wav', + * video: 'video/webm', + * gif: 'image/gif' * }; * recorder.startRecording(); * @see For further information: @@ -868,9 +819,9 @@ function MRecordRTC(mediaStream) { * @example * var recorder = new MRecordRTC(); * recorder.mediaType = { - * audio: true, - * video: true, - * gif : true + * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder + * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder + * gif : true // TRUE or GifRecorder * }; */ this.mediaType = { @@ -886,34 +837,99 @@ function MRecordRTC(mediaStream) { * recorder.startRecording(); */ this.startRecording = function() { - if (!isChrome && mediaStream && mediaStream.getAudioTracks && mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) { + var mediaType = this.mediaType; + var recorderType; + var mimeType = this.mimeType || { + audio: null, + video: null, + gif: null + }; + + if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && mediaStream && mediaStream.getAudioTracks && mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) { // Firefox is supporting both audio/video in single blob this.mediaType.audio = false; } - if (this.mediaType.audio) { + if (!!mediaType.audio) { + recorderType = null; + if (typeof mediaType.audio === 'function') { + recorderType = mediaType.audio; + } this.audioRecorder = new RecordRTC(mediaStream, { type: 'audio', bufferSize: this.bufferSize, - sampleRate: this.sampleRate + sampleRate: this.sampleRate, + numberOfAudioChannels: this.numberOfAudioChannels || 2, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.audio }); - this.audioRecorder.startRecording(); + if (!mediaType.video) { + this.audioRecorder.startRecording(); + } } - if (this.mediaType.video) { - this.videoRecorder = new RecordRTC(mediaStream, { + if (!!mediaType.video) { + recorderType = null; + if (typeof mediaType.video === 'function') { + recorderType = mediaType.video; + } + + var newStream = mediaStream; + + if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { + var videoTrack = mediaStream.getVideoTracks()[0]; + + if (!!navigator.mozGetUserMedia) { + newStream = new MediaStream(); + newStream.addTrack(videoTrack); + + if (recorderType && recorderType === WhammyRecorder) { + // Firefox is NOT supporting webp-encoding yet + recorderType = MediaStreamRecorder; + } + } else { + newStream = new MediaStream([videoTrack]); + } + } + + this.videoRecorder = new RecordRTC(newStream, { type: 'video', video: this.video, - canvas: this.canvas + canvas: this.canvas, + frameInterval: this.frameInterval || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.video + }); + if (!mediaType.audio) { + this.videoRecorder.startRecording(); + } + } + + if (!!mediaType.audio && !!mediaType.video) { + var self = this; + self.videoRecorder.initRecorder(function() { + self.audioRecorder.initRecorder(function() { + // Both recorders are ready to record things accurately + self.videoRecorder.startRecording(); + self.audioRecorder.startRecording(); + }); }); - this.videoRecorder.startRecording(); } - if (this.mediaType.gif) { + if (!!mediaType.gif) { + recorderType = null; + if (typeof mediaType.gif === 'function') { + recorderType = mediaType.gif; + } this.gifRecorder = new RecordRTC(mediaStream, { type: 'gif', frameRate: this.frameRate || 200, - quality: this.quality || 10 + quality: this.quality || 10, + disableLogs: this.disableLogs, + recorderType: recorderType, + mimeType: mimeType.gif }); this.gifRecorder.startRecording(); } @@ -1212,16 +1228,52 @@ if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefine } /*global MediaStream:true */ -if (typeof MediaStream !== 'undefined' && !('stop' in MediaStream.prototype)) { - MediaStream.prototype.stop = function() { - this.getAudioTracks().forEach(function(track) { - track.stop(); - }); +if (typeof MediaStream !== 'undefined') { + if (!('getVideoTracks' in MediaStream.prototype)) { + MediaStream.prototype.getVideoTracks = function() { + if (!this.getTracks) { + return []; + } - this.getVideoTracks().forEach(function(track) { - track.stop(); - }); - }; + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('video') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; + + MediaStream.prototype.getAudioTracks = function() { + if (!this.getTracks) { + return []; + } + + var tracks = []; + this.getTracks.forEach(function(track) { + if (track.kind.toString().indexOf('audio') !== -1) { + tracks.push(track); + } + }); + return tracks; + }; + } + + if (!('stop' in MediaStream.prototype)) { + MediaStream.prototype.stop = function() { + this.getAudioTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + + this.getVideoTracks().forEach(function(track) { + if (!!track.stop) { + track.stop(); + } + }); + }; + } } if (typeof location !== 'undefined') { @@ -1261,10 +1313,12 @@ function invokeSaveAsDialog(file, fileName) { } if (!file.type) { - file.type = 'video/webm'; + try { + file.type = 'video/webm'; + } catch (e) {} } - var fileExtension = file.type.split('/')[1]; + var fileExtension = (file.type || 'video/webm').split('/')[1]; if (fileName && fileName.indexOf('.') !== -1) { var splitted = fileName.split('.'); @@ -1326,6 +1380,49 @@ if (typeof AudioContext !== 'undefined') { Storage.AudioContext = webkitAudioContext; } +function isMediaRecorderCompatible() { + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isChrome = !!window.chrome && !isOpera; + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + + if (isFirefox) { + return true; + } + + if (!isChrome) { + return false; + } + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + if (isChrome) { + verOffset = nAgt.indexOf('Chrome'); + fullVersion = nAgt.substring(verOffset + 7); + } + + // trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return majorVersion >= 49; +} + // ______________________ // MediaStreamRecorder.js @@ -1353,9 +1450,10 @@ if (typeof AudioContext !== 'undefined') { * @example * var options = { * mimeType: 'video/mp4', // audio/ogg or video/webm - * audioBitsPerSecond : 128000, - * videoBitsPerSecond : 2500000, - * bitsPerSecond: 2500000 // if this is provided, skip above two + * audioBitsPerSecond : 256 * 8 * 1024, + * videoBitsPerSecond : 256 * 8 * 1024, + * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two + * getNativeBlob: true // by default it is false * } * var recorder = new MediaStreamRecorder(MediaStream, options); * recorder.record(); @@ -1371,32 +1469,31 @@ if (typeof AudioContext !== 'undefined') { */ function MediaStreamRecorder(mediaStream, config) { + var self = this; + config = config || { - bitsPerSecond: 128000, + // bitsPerSecond: 256 * 8 * 1024, mimeType: 'video/webm' }; - // if user chosen only audio option; and he tried to pass MediaStream with - // both audio and video tracks; - // using a dirty workaround to generate audio-only stream so that we can get audio/ogg output. - if (!isChrome && config.type && config.type === 'audio') { - if (mediaStream.getVideoTracks && mediaStream.getVideoTracks().length) { - var context = new AudioContext(); - var mediaStreamSource = context.createMediaStreamSource(mediaStream); - - var destination = context.createMediaStreamDestination(); - mediaStreamSource.connect(destination); - - mediaStream = destination.stream; + if (config.type === 'audio') { + if (mediaStream.getVideoTracks().length && mediaStream.getAudioTracks().length) { + var stream; + if (!!navigator.mozGetUserMedia) { + stream = new MediaStream(); + stream.addTrack(mediaStream.getAudioTracks()[0]); + } else { + // webkitMediaStream + stream = new MediaStream(mediaStream.getAudioTracks()); + } + mediaStream = stream; } if (!config.mimeType || config.mimeType.indexOf('audio') === -1) { - config.mimeType = 'audio/ogg'; + config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg'; } } - var recordedBuffers = []; - /** * This method records MediaStream. * @method @@ -1405,16 +1502,9 @@ function MediaStreamRecorder(mediaStream, config) { * recorder.record(); */ this.record = function() { - var recorderHints = config; - - if (isChrome) { - if (!recorderHints || typeof recorderHints !== 'string') { - recorderHints = 'video/vp8'; + self.blob = null; - // chrome currently supports only video recording - mediaStream = new MediaStream(mediaStream.getVideoTracks()); - } - } + var recorderHints = config; if (!config.disableLogs) { console.log('Passing following config over MediaRecorder API.', recorderHints); @@ -1425,6 +1515,11 @@ function MediaStreamRecorder(mediaStream, config) { mediaRecorder = null; } + if (isChrome && !isMediaRecorderCompatible()) { + // to support video-only recording on stable + recorderHints = 'video/vp8'; + } + // http://dxr.mozilla.org/mozilla-central/source/content/media/MediaRecorder.cpp // https://wiki.mozilla.org/Gecko:MediaRecorder // https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html @@ -1445,16 +1540,29 @@ function MediaStreamRecorder(mediaStream, config) { // Dispatching OnDataAvailable Handler mediaRecorder.ondataavailable = function(e) { - if (this.dontFireOnDataAvailableEvent) { + if (self.dontFireOnDataAvailableEvent) { return; } - if (isChrome && e.data && !('size' in e.data)) { - e.data.size = e.data.length || e.data.byteLength || 0; + if (!e.data || !e.data.size || e.data.size < 100 || self.blob) { + return; } - if (e.data && e.data.size) { - recordedBuffers.push(e.data); + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof MediaStreamRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + self.blob = config.getNativeBlob ? e.data : new Blob([e.data], { + type: config.mimeType || 'video/webm' + }); + + if (self.recordingCallback) { + self.recordingCallback(self.blob); + self.recordingCallback = null; } }; @@ -1484,7 +1592,6 @@ function MediaStreamRecorder(mediaStream, config) { if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') { mediaRecorder.stop(); } - // self.record(0); }; // void start(optional long mTimeSlice) @@ -1492,7 +1599,7 @@ function MediaStreamRecorder(mediaStream, config) { // handler. "mTimeSlice < 0" means Session object does not push encoded data to // onDataAvailable, instead, it passive wait the client side pull encoded data // by calling requestData API. - mediaRecorder.start(1); + mediaRecorder.start(3.6e+6); // Start recording. If timeSlice has been provided, mediaRecorder will // raise a dataavailable event containing the Blob of collected data on every timeSlice milliseconds. @@ -1532,28 +1639,6 @@ function MediaStreamRecorder(mediaStream, config) { mediaRecorder.requestData(); mediaRecorder.stop(); } - - if (recordedBuffers.length) { - this.onRecordingFinished(); - } - }; - - this.onRecordingFinished = function() { - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof MediaStreamRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - this.blob = new Blob(recordedBuffers, { - type: config.mimeType || 'video/webm' - }); - - this.recordingCallback(); - - recordedBuffers = []; }; /** @@ -1583,7 +1668,11 @@ function MediaStreamRecorder(mediaStream, config) { this.resume = function() { if (this.dontFireOnDataAvailableEvent) { this.dontFireOnDataAvailableEvent = false; + + var disableLogs = config.disableLogs; + config.disableLogs = true; this.record(); + config.disableLogs = disableLogs; return; } @@ -1627,6 +1716,7 @@ function MediaStreamRecorder(mediaStream, config) { return false; } } + return true; } var self = this; @@ -1714,6 +1804,7 @@ function StereoAudioRecorder(mediaStream, config) { return false; } } + return true; } /** @@ -1844,7 +1935,7 @@ function StereoAudioRecorder(mediaStream, config) { view.setUint32(24, sampleRate, true); // byte rate (sample rate * block align) - view.setUint32(28, sampleRate * 4, true); + view.setUint32(28, sampleRate * 2, true); // block align (channel count * bytes per sample) view.setUint16(32, numberOfAudioChannels * 2, true); @@ -2194,11 +2285,14 @@ function StereoAudioRecorder(mediaStream, config) { */ function CanvasRecorder(htmlElement, config) { - if (typeof html2canvas === 'undefined') { + if (typeof html2canvas === 'undefined' && htmlElement.nodeName.toLowerCase() !== 'canvas') { throw 'Please link: //cdn.webrtc-experiment.com/screenshot.js'; } config = config || {}; + if (!config.frameInterval) { + config.frameInterval = 10; + } // via DetectRTC.js var isCanvasSupportsStreamCapturing = false; @@ -2208,6 +2302,10 @@ function CanvasRecorder(htmlElement, config) { } }); + if (!!window.webkitRTCPeerConnection || !!window.webkitGetUserMedia) { + isCanvasSupportsStreamCapturing = false; + } + var globalCanvas, globalContext, mediaStreamRecorder; if (isCanvasSupportsStreamCapturing) { @@ -2240,6 +2338,8 @@ function CanvasRecorder(htmlElement, config) { * recorder.record(); */ this.record = function() { + isRecording = true; + if (isCanvasSupportsStreamCapturing) { // CanvasCaptureMediaStream var canvasMediaStream; @@ -2251,26 +2351,57 @@ function CanvasRecorder(htmlElement, config) { canvasMediaStream = globalCanvas.captureStream(25); } + try { + var mdStream = new MediaStream(); + mdStream.addTrack(canvasMediaStream.getVideoTracks()[0]); + canvasMediaStream = mdStream; + } catch (e) {} + if (!canvasMediaStream) { throw 'captureStream API are NOT available.'; } - // Note: Sep, 2015 status is that, MediaRecorder API can't record CanvasCaptureMediaStream object. + // Note: Jan 18, 2016 status is that, + // Firefox MediaRecorder API can't record CanvasCaptureMediaStream object. mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, { mimeType: 'video/webm' }); mediaStreamRecorder.record(); + } else { + whammy.frames = []; + lastTime = new Date().getTime(); + drawCanvasFrame(); } - isRecording = true; - whammy.frames = []; - drawCanvasFrame(); - if (config.initCallback) { config.initCallback(); } }; + this.getWebPImages = function(callback) { + if (htmlElement.nodeName.toLowerCase() !== 'canvas') { + callback(); + return; + } + + var framesLength = whammy.frames.length; + whammy.frames.forEach(function(frame, idx) { + var framesRemaining = framesLength - idx; + document.title = framesRemaining + '/' + framesLength + ' frames remaining'; + + if (config.onEncodingCallback) { + config.onEncodingCallback(framesRemaining, framesLength); + } + + var webp = frame.image.toDataURL('image/webp', 1); + whammy.frames[idx].image = webp; + }); + + document.title = 'Generating WebM'; + + callback(); + }; + /** * This method stops recording Canvas. * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee. @@ -2284,6 +2415,8 @@ function CanvasRecorder(htmlElement, config) { this.stop = function(callback) { isRecording = false; + var that = this; + if (isCanvasSupportsStreamCapturing && mediaStreamRecorder) { var slef = this; mediaStreamRecorder.stop(function() { @@ -2297,30 +2430,32 @@ function CanvasRecorder(htmlElement, config) { return; } - var that = this; + this.getWebPImages(function() { + /** + * @property {Blob} blob - Recorded frames in video/webm blob. + * @memberof CanvasRecorder + * @example + * recorder.stop(function() { + * var blob = recorder.blob; + * }); + */ + whammy.compile(function(blob) { + document.title = 'Recording finished!'; - /** - * @property {Blob} blob - Recorded frames in video/webm blob. - * @memberof CanvasRecorder - * @example - * recorder.stop(function() { - * var blob = recorder.blob; - * }); - */ - whammy.compile(function(blob) { - that.blob = blob; + that.blob = blob; - if (that.blob.forEach) { - that.blob = new Blob([], { - type: 'video/webm' - }); - } + if (that.blob.forEach) { + that.blob = new Blob([], { + type: 'video/webm' + }); + } - if (callback) { - callback(that.blob); - } + if (callback) { + callback(that.blob); + } - whammy.frames = []; + whammy.frames = []; + }); }); }; @@ -2360,40 +2495,62 @@ function CanvasRecorder(htmlElement, config) { whammy.frames = []; }; + function cloneCanvas() { + //create a new canvas + var newCanvas = document.createElement('canvas'); + var context = newCanvas.getContext('2d'); + + //set dimensions + newCanvas.width = htmlElement.width; + newCanvas.height = htmlElement.height; + + //apply the old canvas to the new one + context.drawImage(htmlElement, 0, 0); + + //return the new canvas + return newCanvas; + } + function drawCanvasFrame() { if (isPausedRecording) { lastTime = new Date().getTime(); - return setTimeout(drawCanvasFrame, 100); + return setTimeout(drawCanvasFrame, 500); + } + + if (htmlElement.nodeName.toLowerCase() === 'canvas') { + var duration = new Date().getTime() - lastTime; + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); + + whammy.frames.push({ + image: cloneCanvas(), + duration: duration + }); + + if (isRecording) { + setTimeout(drawCanvasFrame, config.frameInterval); + } + return; } html2canvas(htmlElement, { + grabMouse: typeof config.showMousePointer === 'undefined' || config.showMousePointer, onrendered: function(canvas) { - if (isCanvasSupportsStreamCapturing) { - var image = document.createElement('img'); - image.src = canvas.toDataURL('image/png'); - image.onload = function() { - globalContext.drawImage(image, 0, 0, image.clientWidth, image.clientHeight); - (document.body || document.documentElement).removeChild(image); - }; - image.style.opacity = 0; - (document.body || document.documentElement).appendChild(image); - } else { - var duration = new Date().getTime() - lastTime; - if (!duration) { - return drawCanvasFrame(); - } + var duration = new Date().getTime() - lastTime; + if (!duration) { + return setTimeout(drawCanvasFrame, config.frameInterval); + } - // via #206, by Jack i.e. @Seymourr - lastTime = new Date().getTime(); + // via #206, by Jack i.e. @Seymourr + lastTime = new Date().getTime(); - whammy.frames.push({ - duration: duration, - image: canvas.toDataURL('image/webp') - }); - } + whammy.frames.push({ + image: canvas.toDataURL('image/webp', 1), + duration: duration + }); if (isRecording) { - setTimeout(drawCanvasFrame, 0); + setTimeout(drawCanvasFrame, config.frameInterval); } } }); @@ -3151,10 +3308,6 @@ var Whammy = (function() { return webp; })); - if (!!navigator.mozGetUserMedia) { - return webm; - } - postMessage(webm); } @@ -3170,10 +3323,6 @@ var Whammy = (function() { * }); */ WhammyVideo.prototype.compile = function(callback) { - if (!!navigator.mozGetUserMedia) { - callback(whammyInWebWorker(this.frames)); - return; - } var webWorker = processInWebWorker(whammyInWebWorker); webWorker.onmessage = function(event) { diff --git a/RecordRTC/RecordRTC.min.js b/RecordRTC/RecordRTC.min.js index ab06a846..7a87b7c9 100644 --- a/RecordRTC/RecordRTC.min.js +++ b/RecordRTC/RecordRTC.min.js @@ -1,2 +1,6 @@ -"use strict";function RecordRTC(mediaStream,config){function startRecording(){return config.disableLogs||console.debug("started recording "+config.type+" stream."),mediaRecorder?(mediaRecorder.clearRecordedData(),mediaRecorder.resume(),self.recordingDuration&&handleRecordingDuration(),self):(initRecorder(function(){self.recordingDuration&&handleRecordingDuration()}),self)}function initRecorder(initCallback){config.disableLogs||console.debug("initializing "+config.type+" stream recorder."),initCallback&&(config.initCallback=function(){initCallback(),initCallback=config.initCallback=null});var Recorder=new GetRecorderType(mediaStream,config);mediaRecorder=new Recorder(mediaStream,config),mediaRecorder.record()}function stopRecording(callback){function _callback(){for(var item in mediaRecorder)self&&(self[item]=mediaRecorder[item]),recordRTC&&(recordRTC[item]=mediaRecorder[item]);var blob=mediaRecorder.blob;if(callback){var url=URL.createObjectURL(blob);callback(url)}blob&&!config.disableLogs&&console.debug(blob.type,"->",bytesToSize(blob.size)),config.autoWriteToDisk&&getDataURL(function(dataURL){var parameter={};parameter[config.type+"Blob"]=dataURL,DiskStorage.Store(parameter)})}if(!mediaRecorder)return console.warn(WARNING);var recordRTC=this;config.disableLogs||console.warn("Stopped recording "+config.type+" stream."),"gif"!==config.type?mediaRecorder.stop(_callback):(mediaRecorder.stop(),_callback())}function pauseRecording(){return mediaRecorder?(mediaRecorder.pause(),void(config.disableLogs||console.debug("Paused recording."))):console.warn(WARNING)}function resumeRecording(){return mediaRecorder?(mediaRecorder.resume(),void(config.disableLogs||console.debug("Resumed recording."))):console.warn(WARNING)}function getDataURL(callback,_mediaRecorder){function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}if(!callback)throw"Pass a callback function over getDataURL.";var blob=_mediaRecorder?_mediaRecorder.blob:mediaRecorder.blob;if(!blob)return config.disableLogs||console.warn("Blob encoder did not yet finished its job."),void setTimeout(function(){getDataURL(callback,_mediaRecorder)},1e3);if("undefined"!=typeof Worker){var webWorker=processInWebWorker(function(_blob){postMessage((new FileReaderSync).readAsDataURL(_blob))});webWorker.onmessage=function(event){callback(event.data)},webWorker.postMessage(blob)}else{var reader=new FileReader;reader.readAsDataURL(blob),reader.onload=function(event){callback(event.target.result)}}}function handleRecordingDuration(){setTimeout(function(){stopRecording(self.onRecordingStopped)},self.recordingDuration)}if(!mediaStream)throw"MediaStream is mandatory.";config=new RecordRTCConfiguration(mediaStream,config);var mediaRecorder,self=this,WARNING='It seems that "startRecording" is not invoked for '+config.type+" recorder.",returnObject={startRecording:startRecording,stopRecording:stopRecording,pauseRecording:pauseRecording,resumeRecording:resumeRecording,initRecorder:initRecorder,setRecordingDuration:function(milliseconds,callback){if("undefined"==typeof milliseconds)throw"milliseconds is required.";if("number"!=typeof milliseconds)throw"milliseconds must be a number.";return self.recordingDuration=milliseconds,self.onRecordingStopped=callback||function(){},{onRecordingStopped:function(callback){self.onRecordingStopped=callback}}},clearRecordedData:function(){return mediaRecorder?(mediaRecorder.clearRecordedData(),void(config.disableLogs||console.debug("Cleared old recorded data."))):console.warn(WARNING)},getBlob:function(){return mediaRecorder?mediaRecorder.blob:console.warn(WARNING)},getDataURL:getDataURL,toURL:function(){return mediaRecorder?URL.createObjectURL(mediaRecorder.blob):console.warn(WARNING)},save:function(fileName){return mediaRecorder?void invokeSaveAsDialog(mediaRecorder.blob,fileName):console.warn(WARNING)},getFromDisk:function(callback){return mediaRecorder?void RecordRTC.getFromDisk(config.type,callback):console.warn(WARNING)},setAdvertisementArray:function(arrayOfWebPImages){config.advertisement=[];for(var length=arrayOfWebPImages.length,i=0;length>i;i++)config.advertisement.push({duration:i,image:arrayOfWebPImages[i]})},blob:null,bufferSize:0,sampleRate:0,buffer:null,view:null};if(!this)return self=returnObject,returnObject;for(var prop in returnObject)this[prop]=returnObject[prop];return self=this,returnObject}function RecordRTCConfiguration(mediaStream,config){return config.recorderType&&!config.type&&(config.recorderType===WhammyRecorder||config.recorderType===CanvasRecorder?config.type="video":config.recorderType===GifRecorder?config.type="gif":config.recorderType===StereoAudioRecorder?config.type="audio":config.recorderType===MediaStreamRecorder&&(mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length?config.type="video":mediaStream.getAudioTracks().length&&!mediaStream.getVideoTracks().length?config.type="audio":!mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length&&(config.type="audio"))),"undefined"!=typeof MediaStreamRecorder&&"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&(config.mimeType||(config.mimeType="video/webm"),config.type||(config.type=config.mimeType.split("/")[0]),config.bitsPerSecond||(config.bitsPerSecond=128e3)),config.type||(config.mimeType&&(config.type=config.mimeType.split("/")[0]),config.type||(config.type="audio")),config}function GetRecorderType(mediaStream,config){var recorder;return(isChrome||isEdge||isOpera)&&(recorder=StereoAudioRecorder),"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&!isChrome&&(recorder=MediaStreamRecorder),"video"===config.type&&(isChrome||isOpera)&&(recorder=WhammyRecorder),"gif"===config.type&&(recorder=GifRecorder),"canvas"===config.type&&(recorder=CanvasRecorder),config.recorderType&&(recorder=config.recorderType),recorder}function MRecordRTC(mediaStream){this.addStream=function(_mediaStream){_mediaStream&&(mediaStream=_mediaStream)},this.mediaType={audio:!0,video:!0},this.startRecording=function(){!isChrome&&mediaStream&&mediaStream.getAudioTracks&&mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length&&(this.mediaType.audio=!1),this.mediaType.audio&&(this.audioRecorder=new RecordRTC(mediaStream,{type:"audio",bufferSize:this.bufferSize,sampleRate:this.sampleRate}),this.audioRecorder.startRecording()),this.mediaType.video&&(this.videoRecorder=new RecordRTC(mediaStream,{type:"video",video:this.video,canvas:this.canvas}),this.videoRecorder.startRecording()),this.mediaType.gif&&(this.gifRecorder=new RecordRTC(mediaStream,{type:"gif",frameRate:this.frameRate||200,quality:this.quality||10}),this.gifRecorder.startRecording())},this.stopRecording=function(callback){callback=callback||function(){},this.audioRecorder&&this.audioRecorder.stopRecording(function(blobURL){callback(blobURL,"audio")}),this.videoRecorder&&this.videoRecorder.stopRecording(function(blobURL){callback(blobURL,"video")}),this.gifRecorder&&this.gifRecorder.stopRecording(function(blobURL){callback(blobURL,"gif")})},this.getBlob=function(callback){var output={};return this.audioRecorder&&(output.audio=this.audioRecorder.getBlob()),this.videoRecorder&&(output.video=this.videoRecorder.getBlob()),this.gifRecorder&&(output.gif=this.gifRecorder.getBlob()),callback&&callback(output),output},this.getDataURL=function(callback){function getDataURL(blob,callback00){if("undefined"!=typeof Worker){var webWorker=processInWebWorker(function(_blob){postMessage((new FileReaderSync).readAsDataURL(_blob))});webWorker.onmessage=function(event){callback00(event.data)},webWorker.postMessage(blob)}else{var reader=new FileReader;reader.readAsDataURL(blob),reader.onload=function(event){callback00(event.target.result)}}}function processInWebWorker(_function){var url,blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);if("undefined"!=typeof URL)url=URL;else{if("undefined"==typeof webkitURL)throw"Neither URL nor webkitURL detected.";url=webkitURL}return url.revokeObjectURL(blob),worker}this.getBlob(function(blob){getDataURL(blob.audio,function(_audioDataURL){getDataURL(blob.video,function(_videoDataURL){callback({audio:_audioDataURL,video:_videoDataURL})})})})},this.writeToDisk=function(){RecordRTC.writeToDisk({audio:this.audioRecorder,video:this.videoRecorder,gif:this.gifRecorder})},this.save=function(args){args=args||{audio:!0,video:!0,gif:!0},args.audio&&this.audioRecorder&&this.audioRecorder.save("string"==typeof args.audio?args.audio:""),args.video&&this.videoRecorder&&this.videoRecorder.save("string"==typeof args.video?args.video:""),args.gif&&this.gifRecorder&&this.gifRecorder.save("string"==typeof args.gif?args.gif:"")}}function bytesToSize(bytes){var k=1e3,sizes=["Bytes","KB","MB","GB","TB"];if(0===bytes)return"0 Bytes";var i=parseInt(Math.floor(Math.log(bytes)/Math.log(k)),10);return(bytes/Math.pow(k,i)).toPrecision(3)+" "+sizes[i]}function invokeSaveAsDialog(file,fileName){if(!file)throw"Blob object is required.";file.type||(file.type="video/webm");var fileExtension=file.type.split("/")[1];if(fileName&&-1!==fileName.indexOf(".")){var splitted=fileName.split(".");fileName=splitted[0],fileExtension=splitted[1]}var fileFullName=(fileName||Math.round(9999999999*Math.random())+888888888)+"."+fileExtension;if("undefined"!=typeof navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(file,fileFullName);if("undefined"!=typeof navigator.msSaveBlob)return navigator.msSaveBlob(file,fileFullName);var hyperlink=document.createElement("a");hyperlink.href=URL.createObjectURL(file),hyperlink.target="_blank",hyperlink.download=fileFullName,navigator.mozGetUserMedia&&(hyperlink.onclick=function(){(document.body||document.documentElement).removeChild(hyperlink)},(document.body||document.documentElement).appendChild(hyperlink));var evt=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0});hyperlink.dispatchEvent(evt),navigator.mozGetUserMedia||URL.revokeObjectURL(hyperlink.href)}function MediaStreamRecorder(mediaStream,config){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1}if(config=config||{bitsPerSecond:128e3,mimeType:"video/webm"},!isChrome&&config.type&&"audio"===config.type){if(mediaStream.getVideoTracks&&mediaStream.getVideoTracks().length){var context=new AudioContext,mediaStreamSource=context.createMediaStreamSource(mediaStream),destination=context.createMediaStreamDestination();mediaStreamSource.connect(destination),mediaStream=destination.stream}config.mimeType&&-1!==config.mimeType.indexOf("audio")||(config.mimeType="audio/ogg")}var recordedBuffers=[];this.record=function(){var recorderHints=config;isChrome&&(recorderHints&&"string"==typeof recorderHints||(recorderHints="video/vp8",mediaStream=new MediaStream(mediaStream.getVideoTracks()))),config.disableLogs||console.log("Passing following config over MediaRecorder API.",recorderHints),mediaRecorder&&(mediaRecorder=null),mediaRecorder=new MediaRecorder(mediaStream,recorderHints),"canRecordMimeType"in mediaRecorder&&mediaRecorder.canRecordMimeType(config.mimeType)===!1&&(config.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",config.mimeType)),mediaRecorder.ignoreMutedMedia=config.ignoreMutedMedia||!1,mediaRecorder.ondataavailable=function(e){this.dontFireOnDataAvailableEvent||(!isChrome||!e.data||"size"in e.data||(e.data.size=e.data.length||e.data.byteLength||0),e.data&&e.data.size&&recordedBuffers.push(e.data))},mediaRecorder.onerror=function(error){config.disableLogs||("InvalidState"===error.name?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed."):"OutOfMemory"===error.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute."):"IllegalStreamModification"===error.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute."):"OtherRecordingError"===error.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute."):"GenericError"===error.name?console.error("The UA cannot provide the codec or recording option that has been requested.",error):console.error("MediaRecorder Error",error)),"inactive"!==mediaRecorder.state&&"stopped"!==mediaRecorder.state&&mediaRecorder.stop()},mediaRecorder.start(1),config.onAudioProcessStarted&&config.onAudioProcessStarted(),config.initCallback&&config.initCallback()},this.stop=function(callback){mediaRecorder&&(this.recordingCallback=callback||function(){},"recording"===mediaRecorder.state&&(mediaRecorder.requestData(),mediaRecorder.stop()),recordedBuffers.length&&this.onRecordingFinished())},this.onRecordingFinished=function(){this.blob=new Blob(recordedBuffers,{type:config.mimeType||"video/webm"}),this.recordingCallback(),recordedBuffers=[]},this.pause=function(){mediaRecorder&&"recording"===mediaRecorder.state&&mediaRecorder.pause()},this.resume=function(){return this.dontFireOnDataAvailableEvent?(this.dontFireOnDataAvailableEvent=!1,void this.record()):void(mediaRecorder&&"paused"===mediaRecorder.state&&mediaRecorder.resume())},this.clearRecordedData=function(){mediaRecorder&&(this.pause(),this.dontFireOnDataAvailableEvent=!0,this.stop())};var mediaRecorder,self=this;!function looper(){return mediaRecorder?isMediaStreamActive()===!1?void self.stop():void setTimeout(looper,1e3):void 0}()}function StereoAudioRecorder(mediaStream,config){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1}function mergeLeftRightBuffers(config,callback){function mergeAudioBuffers(config,cb){function mergeBuffers(channelBuffer,rLength){for(var result=new Float64Array(rLength),offset=0,lng=channelBuffer.length,i=0;lng>i;i++){var buffer=channelBuffer[i];result.set(buffer,offset),offset+=buffer.length}return result}function interleave(leftChannel,rightChannel){for(var length=leftChannel.length+rightChannel.length,result=new Float64Array(length),inputIndex=0,index=0;length>index;)result[index++]=leftChannel[inputIndex],result[index++]=rightChannel[inputIndex],inputIndex++;return result}function writeUTFBytes(view,offset,string){for(var lng=string.length,i=0;lng>i;i++)view.setUint8(offset+i,string.charCodeAt(i))}var numberOfAudioChannels=config.numberOfAudioChannels,leftBuffers=config.leftBuffers.slice(0),rightBuffers=config.rightBuffers.slice(0),sampleRate=config.sampleRate,internalInterleavedLength=config.internalInterleavedLength;2===numberOfAudioChannels&&(leftBuffers=mergeBuffers(leftBuffers,internalInterleavedLength),rightBuffers=mergeBuffers(rightBuffers,internalInterleavedLength)),1===numberOfAudioChannels&&(leftBuffers=mergeBuffers(leftBuffers,internalInterleavedLength));var interleaved;2===numberOfAudioChannels&&(interleaved=interleave(leftBuffers,rightBuffers)),1===numberOfAudioChannels&&(interleaved=leftBuffers);var interleavedLength=interleaved.length,resultingBufferLength=44+2*interleavedLength,buffer=new ArrayBuffer(resultingBufferLength),view=new DataView(buffer);writeUTFBytes(view,0,"RIFF"),view.setUint32(4,44+2*interleavedLength,!0),writeUTFBytes(view,8,"WAVE"),writeUTFBytes(view,12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,numberOfAudioChannels,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,4*sampleRate,!0),view.setUint16(32,2*numberOfAudioChannels,!0),view.setUint16(34,16,!0),writeUTFBytes(view,36,"data"),view.setUint32(40,2*interleavedLength,!0);for(var lng=interleavedLength,index=44,volume=1,i=0;lng>i;i++)view.setInt16(index,32767*interleaved[i]*volume,!0),index+=2;return cb?cb({buffer:buffer,view:view}):void postMessage({buffer:buffer,view:view})}if(!isChrome)return void mergeAudioBuffers(config,function(data){callback(data.buffer,data.view)});var webWorker=processInWebWorker(mergeAudioBuffers);webWorker.onmessage=function(event){callback(event.data.buffer,event.data.view),URL.revokeObjectURL(webWorker.workerURL)},webWorker.postMessage(config)}function processInWebWorker(_function){var workerURL=URL.createObjectURL(new Blob([_function.toString(),";this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(workerURL);return worker.workerURL=workerURL,worker}function onAudioProcessDataAvailable(e){if(!isPaused){if(isMediaStreamActive()===!1&&(config.disableLogs||console.error("MediaStream seems stopped."),jsAudioNode.disconnect(),recording=!1),!recording)return void audioInput.disconnect();isAudioProcessStarted||(isAudioProcessStarted=!0,config.onAudioProcessStarted&&config.onAudioProcessStarted(),config.initCallback&&config.initCallback());var left=e.inputBuffer.getChannelData(0);if(leftchannel.push(new Float32Array(left)),2===numberOfAudioChannels){var right=e.inputBuffer.getChannelData(1);rightchannel.push(new Float32Array(right))}recordingLength+=bufferSize}}if(!mediaStream.getAudioTracks().length)throw"Your stream has no audio tracks.";config=config||{};var jsAudioNode,self=this,leftchannel=[],rightchannel=[],recording=!1,recordingLength=0,numberOfAudioChannels=2;config.leftChannel===!0&&(numberOfAudioChannels=1),1===config.numberOfAudioChannels&&(numberOfAudioChannels=1),config.disableLogs||console.debug("StereoAudioRecorder is set to record number of channels: ",numberOfAudioChannels),this.record=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";leftchannel.length=rightchannel.length=0,recordingLength=0,audioInput&&audioInput.connect(jsAudioNode),isAudioProcessStarted=isPaused=!1,recording=!0},this.stop=function(callback){recording=!1,mergeLeftRightBuffers({sampleRate:sampleRate,numberOfAudioChannels:numberOfAudioChannels,internalInterleavedLength:recordingLength,leftBuffers:leftchannel,rightBuffers:1===numberOfAudioChannels?[]:rightchannel},function(buffer,view){self.blob=new Blob([view],{type:"audio/wav"}),self.buffer=new ArrayBuffer(view),self.view=view,self.sampleRate=sampleRate,self.bufferSize=bufferSize,self.length=recordingLength,callback&&callback(),isAudioProcessStarted=!1})},Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext);var context=Storage.AudioContextConstructor,audioInput=context.createMediaStreamSource(mediaStream),legalBufferValues=[0,256,512,1024,2048,4096,8192,16384],bufferSize="undefined"==typeof config.bufferSize?4096:config.bufferSize;if(-1===legalBufferValues.indexOf(bufferSize)&&(config.disableLogs||console.warn("Legal values for buffer-size are "+JSON.stringify(legalBufferValues,null," "))),context.createJavaScriptNode)jsAudioNode=context.createJavaScriptNode(bufferSize,numberOfAudioChannels,numberOfAudioChannels);else{if(!context.createScriptProcessor)throw"WebAudio API has no support on this browser.";jsAudioNode=context.createScriptProcessor(bufferSize,numberOfAudioChannels,numberOfAudioChannels)}audioInput.connect(jsAudioNode),config.bufferSize||(bufferSize=jsAudioNode.bufferSize);var sampleRate="undefined"!=typeof config.sampleRate?config.sampleRate:context.sampleRate||44100;(22050>sampleRate||sampleRate>96e3)&&(config.disableLogs||console.warn("sample-rate must be under range 22050 and 96000.")),config.disableLogs||(console.log("sample-rate",sampleRate),console.log("buffer-size",bufferSize));var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";return recording?void(isPaused=!1):(config.disableLogs||console.info("Seems recording has been restarted."),void this.record())},this.clearRecordedData=function(){this.pause(),leftchannel.length=rightchannel.length=0,recordingLength=0};var isAudioProcessStarted=!1;jsAudioNode.onaudioprocess=onAudioProcessDataAvailable,jsAudioNode.connect(context.destination)}function CanvasRecorder(htmlElement,config){function drawCanvasFrame(){return isPausedRecording?(lastTime=(new Date).getTime(),setTimeout(drawCanvasFrame,100)):void html2canvas(htmlElement,{onrendered:function(canvas){if(isCanvasSupportsStreamCapturing){var image=document.createElement("img");image.src=canvas.toDataURL("image/png"),image.onload=function(){globalContext.drawImage(image,0,0,image.clientWidth,image.clientHeight),(document.body||document.documentElement).removeChild(image)},image.style.opacity=0,(document.body||document.documentElement).appendChild(image)}else{var duration=(new Date).getTime()-lastTime;if(!duration)return drawCanvasFrame();lastTime=(new Date).getTime(),whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")})}isRecording&&setTimeout(drawCanvasFrame,0)}})}if("undefined"==typeof html2canvas)throw"Please link: //cdn.webrtc-experiment.com/screenshot.js";config=config||{};var isCanvasSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0)});var globalCanvas,globalContext,mediaStreamRecorder;isCanvasSupportsStreamCapturing?(config.disableLogs||console.debug("Your browser supports both MediRecorder API and canvas.captureStream!"),globalCanvas=document.createElement("canvas"),globalCanvas.width=htmlElement.clientWidth||window.innerWidth,globalCanvas.height=htmlElement.clientHeight||window.innerHeight,globalCanvas.style="top: -9999999; left: -99999999; visibility:hidden; position:absoluted; display: none;",(document.body||document.documentElement).appendChild(globalCanvas),globalContext=globalCanvas.getContext("2d")):navigator.mozGetUserMedia&&(config.disableLogs||alert("Canvas recording is NOT supported in Firefox."));var isRecording;this.record=function(){if(isCanvasSupportsStreamCapturing){var canvasMediaStream;if("captureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"mozCaptureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"webkitCaptureStream"in globalCanvas&&(canvasMediaStream=globalCanvas.captureStream(25)),!canvasMediaStream)throw"captureStream API are NOT available.";mediaStreamRecorder=new MediaStreamRecorder(canvasMediaStream,{mimeType:"video/webm"}),mediaStreamRecorder.record()}isRecording=!0,whammy.frames=[],drawCanvasFrame(),config.initCallback&&config.initCallback()},this.stop=function(callback){if(isRecording=!1,isCanvasSupportsStreamCapturing&&mediaStreamRecorder){return void mediaStreamRecorder.stop(function(){for(var prop in mediaStreamRecorder)self[prop]=mediaStreamRecorder[prop];callback&&callback(that.blob)})}var that=this;whammy.compile(function(blob){that.blob=blob,that.blob.forEach&&(that.blob=new Blob([],{type:"video/webm"})),callback&&callback(that.blob),whammy.frames=[]})};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){this.pause(),whammy.frames=[]};var lastTime=(new Date).getTime(),whammy=new Whammy.Video(100)}function WhammyRecorder(mediaStream,config){function drawFrames(frameInterval){frameInterval="undefined"!=typeof frameInterval?frameInterval:10;var duration=(new Date).getTime()-lastTime;return duration?isPausedRecording?(lastTime=(new Date).getTime(),setTimeout(drawFrames,100)):(lastTime=(new Date).getTime(),video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),void(isStopDrawing||setTimeout(drawFrames,frameInterval,frameInterval))):setTimeout(drawFrames,frameInterval,frameInterval)}function asyncLoop(o){var i=-1,length=o.length,loop=function(){return i++,i===length?void o.callback():void o.functionToLoop(loop,i)};loop()}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance,callback){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=-1===_framesToCheck,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,doNotCheckNext=!1;asyncLoop({length:endCheckFrame,functionToLoop:function(loop,f){var matchPixCount,endPixCheck,maxPixCount,finishImage=function(){!doNotCheckNext&&maxPixCount*frameTolerance>=maxPixCount-matchPixCount||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f])),loop()};if(doNotCheckNext)finishImage();else{var image=new Image;image.onload=function(){context2d.drawImage(image,0,0,canvas.width,canvas.height);var imageData=context2d.getImageData(0,0,canvas.width,canvas.height);matchPixCount=0,endPixCheck=imageData.data.length,maxPixCount=imageData.data.length/4;for(var pix=0;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}finishImage()},image.src=_frames[f].image}},callback:function(){resultFrames=resultFrames.concat(_frames.slice(endCheckFrame)),resultFrames.length<=0&&resultFrames.push(_frames[_frames.length-1]),callback(resultFrames)}})}config=config||{},config.frameInterval||(config.frameInterval=10),config.disableLogs||console.log("Using frames-interval:",config.frameInterval),this.record=function(){config.width||(config.width=320),config.height||(config.height=240),config.video||(config.video={width:config.width,height:config.height}),config.canvas||(config.canvas={width:config.width,height:config.height}),canvas.width=config.canvas.width,canvas.height=config.canvas.height,context=canvas.getContext("2d"),config.video&&config.video instanceof HTMLVideoElement?(video=config.video.cloneNode(),config.initCallback&&config.initCallback()):(video=document.createElement("video"),"undefined"!=typeof video.srcObject?video.srcObject=mediaStream:video.src=URL.createObjectURL(mediaStream),video.onloadedmetadata=function(){config.initCallback&&config.initCallback()},video.width=config.video.width,video.height=config.video.height),video.muted=!0,video.play(),lastTime=(new Date).getTime(),whammy=new Whammy.Video,config.disableLogs||(console.log("canvas resolutions",canvas.width,"*",canvas.height),console.log("video width/height",video.width||canvas.width,"*",video.height||canvas.height)),drawFrames(config.frameInterval)};var isStopDrawing=!1;this.stop=function(callback){isStopDrawing=!0;var _this=this;setTimeout(function(){dropBlackFrames(whammy.frames,-1,null,null,function(frames){whammy.frames=frames,config.advertisement&&config.advertisement.length&&(whammy.frames=config.advertisement.concat(whammy.frames)),whammy.compile(function(blob){_this.blob=blob,_this.blob.forEach&&(_this.blob=new Blob([],{type:"video/webm"})),callback&&callback(_this.blob)})})},10)};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){this.pause(),whammy.frames=[]};var video,lastTime,whammy,canvas=document.createElement("canvas"),context=canvas.getContext("2d")}function GifRecorder(mediaStream,config){if("undefined"==typeof GIFEncoder)throw"Please link: https://cdn.webrtc-experiment.com/gif-recorder.js";config=config||{};var isHTMLObject=mediaStream instanceof CanvasRenderingContext2D||mediaStream instanceof HTMLCanvasElement;this.record=function(){function drawVideoFrame(time){return isPausedRecording?setTimeout(function(){drawVideoFrame(time)},100):(lastAnimationFrame=requestAnimationFrame(drawVideoFrame),void 0===typeof lastFrameTime&&(lastFrameTime=time),void(90>time-lastFrameTime||(!isHTMLObject&&video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),config.onGifPreview&&config.onGifPreview(canvas.toDataURL("image/png")),gifEncoder.addFrame(context),lastFrameTime=time)))}isHTMLObject||(config.width||(config.width=video.offsetWidth||320),this.height||(config.height=video.offsetHeight||240),config.video||(config.video={width:config.width,height:config.height}),config.canvas||(config.canvas={width:config.width,height:config.height}),canvas.width=config.canvas.width,canvas.height=config.canvas.height,video.width=config.video.width,video.height=config.video.height),gifEncoder=new GIFEncoder,gifEncoder.setRepeat(0),gifEncoder.setDelay(config.frameRate||200),gifEncoder.setQuality(config.quality||10),gifEncoder.start(),startTime=Date.now();lastAnimationFrame=requestAnimationFrame(drawVideoFrame),config.initCallback&&config.initCallback()},this.stop=function(){lastAnimationFrame&&cancelAnimationFrame(lastAnimationFrame),endTime=Date.now(),this.blob=new Blob([new Uint8Array(gifEncoder.stream().bin)],{type:"image/gif"}),gifEncoder.stream().bin=[]};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){gifEncoder&&(this.pause(),gifEncoder.stream().bin=[])};var canvas=document.createElement("canvas"),context=canvas.getContext("2d");if(isHTMLObject&&(mediaStream instanceof CanvasRenderingContext2D?context=mediaStream:mediaStream instanceof HTMLCanvasElement&&(context=mediaStream.getContext("2d"))),!isHTMLObject){var video=document.createElement("video");video.muted=!0,video.autoplay=!0,"undefined"!=typeof video.srcObject?video.srcObject=mediaStream:video.src=URL.createObjectURL(mediaStream),video.play()}var startTime,endTime,lastFrameTime,gifEncoder,lastAnimationFrame=null}RecordRTC.getFromDisk=function(type,callback){if(!callback)throw"callback is mandatory.";console.log("Getting recorded "+("all"===type?"blobs":type+" blob ")+" from disk!"),DiskStorage.Fetch(function(dataURL,_type){"all"!==type&&_type===type+"Blob"&&callback&&callback(dataURL),"all"===type&&callback&&callback(dataURL,_type.replace("Blob",""))})},RecordRTC.writeToDisk=function(options){console.log("Writing recorded blob(s) to disk!"),options=options||{},options.audio&&options.video&&options.gif?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL,gifBlob:gifDataURL})})})}):options.audio&&options.video?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL})})}):options.audio&&options.gif?options.audio.getDataURL(function(audioDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,gifBlob:gifDataURL})})}):options.video&&options.gif?options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({videoBlob:videoDataURL,gifBlob:gifDataURL})})}):options.audio?options.audio.getDataURL(function(audioDataURL){DiskStorage.Store({audioBlob:audioDataURL})}):options.video?options.video.getDataURL(function(videoDataURL){DiskStorage.Store({videoBlob:videoDataURL})}):options.gif&&options.gif.getDataURL(function(gifDataURL){ -DiskStorage.Store({gifBlob:gifDataURL})})},"undefined"!=typeof module&&(module.exports=RecordRTC),"function"==typeof define&&define.amd&&define("RecordRTC",[],function(){return RecordRTC}),MRecordRTC.getFromDisk=RecordRTC.getFromDisk,MRecordRTC.writeToDisk=RecordRTC.writeToDisk;var requestAnimationFrame=window.requestAnimationFrame;"undefined"==typeof requestAnimationFrame&&("undefined"!=typeof webkitRequestAnimationFrame&&(requestAnimationFrame=webkitRequestAnimationFrame),"undefined"!=typeof mozRequestAnimationFrame&&(requestAnimationFrame=mozRequestAnimationFrame));var cancelAnimationFrame=window.cancelAnimationFrame;"undefined"==typeof cancelAnimationFrame&&("undefined"!=typeof webkitCancelAnimationFrame&&(cancelAnimationFrame=webkitCancelAnimationFrame),"undefined"!=typeof mozCancelAnimationFrame&&(cancelAnimationFrame=mozCancelAnimationFrame));var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;if("undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"==typeof navigator)throw'Please make sure to define a global variable named as "navigator"';"undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia);var isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),isOpera=!!window.opera||-1!==navigator.userAgent.indexOf("OPR/"),isChrome=!isOpera&&!isEdge&&!!navigator.webkitGetUserMedia,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"==typeof MediaStream||"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop()}),this.getVideoTracks().forEach(function(track){track.stop()})}),"undefined"!=typeof location&&0===location.href.indexOf("file:")&&console.error("Please load this HTML file on HTTP or HTTPS.");var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext);var Whammy=function(){function WhammyVideo(duration){this.frames=[],this.duration=duration||1,this.quality=.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){function ArrayToWebM(frames){var info=checkFrames(frames);if(!info)return[];for(var clusterMaxDuration=3e4,EBML=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:doubleToString(info.duration),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:info.width,id:176},{data:info.height,id:186}]}]}]}]}],frameNumber=0,clusterTimecode=0;frameNumberclusterDuration);var clusterCounter=0,cluster={id:524531317,data:getClusterData(clusterTimecode,clusterCounter,clusterFrames)};EBML[1].data.push(cluster),clusterTimecode+=clusterDuration}return generateEBML(EBML)}function getClusterData(clusterTimecode,clusterCounter,clusterFrames){return[{data:clusterTimecode,id:231}].concat(clusterFrames.map(function(webp){var block=makeSimpleBlock({discardable:0,frame:webp.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(clusterCounter)});return clusterCounter+=webp.duration,{data:block,id:163}}))}function checkFrames(frames){if(!frames[0])return void postMessage({error:"Something went wrong. Maybe WebP format is not supported in the current browser."});for(var width=frames[0].width,height=frames[0].height,duration=frames[0].duration,i=1;i0;)parts.push(255&num),num>>=8;return new Uint8Array(parts.reverse())}function strToBuffer(str){return new Uint8Array(str.split("").map(function(e){return e.charCodeAt(0)}))}function bitsToBuffer(bits){var data=[],pad=bits.length%8?new Array(9-bits.length%8).join("0"):"";bits=pad+bits;for(var i=0;i127)throw"TrackNumber > 127 not supported";var out=[128|data.trackNum,data.timecode>>8,255&data.timecode,flags].map(function(e){return String.fromCharCode(e)}).join("")+data.frame;return out}function parseWebP(riff){for(var VP8=riff.RIFF[0].WEBP[0],frameStart=VP8.indexOf("*"),i=0,c=[];4>i;i++)c[i]=VP8.charCodeAt(frameStart+3+i);var width,height,tmp;return tmp=c[1]<<8|c[0],width=16383&tmp,tmp=c[3]<<8|c[2],height=16383&tmp,{width:width,height:height,data:VP8,riff:riff}}function getStrLength(string,offset){return parseInt(string.substr(offset+4,4).split("").map(function(i){var unpadded=i.charCodeAt(0).toString(2);return new Array(8-unpadded.length+1).join("0")+unpadded}).join(""),2)}function parseRIFF(string){for(var offset=0,chunks={};offset",bytesToSize(blob.size)),config.autoWriteToDisk&&getDataURL(function(dataURL){var parameter={};parameter[config.type+"Blob"]=dataURL,DiskStorage.Store(parameter)})}if(!mediaRecorder)return console.warn(WARNING);var recordRTC=this;config.disableLogs||console.warn("Stopped recording "+config.type+" stream."),"gif"!==config.type?mediaRecorder.stop(_callback):(mediaRecorder.stop(),_callback())}function pauseRecording(){return mediaRecorder?(mediaRecorder.pause(),void(config.disableLogs||console.debug("Paused recording."))):console.warn(WARNING)}function resumeRecording(){return mediaRecorder?(mediaRecorder.resume(),void(config.disableLogs||console.debug("Resumed recording."))):console.warn(WARNING)}function readFile(_blob){postMessage((new FileReaderSync).readAsDataURL(_blob))}function getDataURL(callback,_mediaRecorder){function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}if(!callback)throw"Pass a callback function over getDataURL.";var blob=_mediaRecorder?_mediaRecorder.blob:mediaRecorder.blob;if(!blob)return config.disableLogs||console.warn("Blob encoder did not yet finished its job."),void setTimeout(function(){getDataURL(callback,_mediaRecorder)},1e3);if("undefined"==typeof Worker||navigator.mozGetUserMedia){var reader=new FileReader;reader.readAsDataURL(blob),reader.onload=function(event){callback(event.target.result)}}else{var webWorker=processInWebWorker(readFile);webWorker.onmessage=function(event){callback(event.data)},webWorker.postMessage(blob)}}function handleRecordingDuration(){setTimeout(function(){stopRecording(self.onRecordingStopped)},self.recordingDuration)}if(!mediaStream)throw"MediaStream is mandatory.";config=new RecordRTCConfiguration(mediaStream,config);var mediaRecorder,self=this,WARNING='It seems that "startRecording" is not invoked for '+config.type+" recorder.",returnObject={startRecording:startRecording,stopRecording:stopRecording,pauseRecording:pauseRecording,resumeRecording:resumeRecording,initRecorder:initRecorder,setRecordingDuration:function(milliseconds,callback){if("undefined"==typeof milliseconds)throw"milliseconds is required.";if("number"!=typeof milliseconds)throw"milliseconds must be a number.";return self.recordingDuration=milliseconds,self.onRecordingStopped=callback||function(){},{onRecordingStopped:function(callback){self.onRecordingStopped=callback}}},clearRecordedData:function(){return mediaRecorder?(mediaRecorder.clearRecordedData(),void(config.disableLogs||console.debug("Cleared old recorded data."))):console.warn(WARNING)},getBlob:function(){return mediaRecorder?mediaRecorder.blob:console.warn(WARNING)},getDataURL:getDataURL,toURL:function(){return mediaRecorder?URL.createObjectURL(mediaRecorder.blob):console.warn(WARNING)},save:function(fileName){return mediaRecorder?void invokeSaveAsDialog(mediaRecorder.blob,fileName):console.warn(WARNING)},getFromDisk:function(callback){return mediaRecorder?void RecordRTC.getFromDisk(config.type,callback):console.warn(WARNING)},setAdvertisementArray:function(arrayOfWebPImages){config.advertisement=[];for(var length=arrayOfWebPImages.length,i=0;length>i;i++)config.advertisement.push({duration:i,image:arrayOfWebPImages[i]})},blob:null,bufferSize:0,sampleRate:0,buffer:null,view:null};if(!this)return self=returnObject,returnObject;for(var prop in returnObject)this[prop]=returnObject[prop];return self=this,returnObject}function RecordRTCConfiguration(mediaStream,config){return config.recorderType&&!config.type&&(config.recorderType===WhammyRecorder||config.recorderType===CanvasRecorder?config.type="video":config.recorderType===GifRecorder?config.type="gif":config.recorderType===StereoAudioRecorder?config.type="audio":config.recorderType===MediaStreamRecorder&&(mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length?config.type="video":mediaStream.getAudioTracks().length&&!mediaStream.getVideoTracks().length?config.type="audio":!mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length&&(config.type="audio"))),"undefined"!=typeof MediaStreamRecorder&&"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&(config.mimeType||(config.mimeType="video/webm"),config.type||(config.type=config.mimeType.split("/")[0]),!config.bitsPerSecond),config.type||(config.mimeType&&(config.type=config.mimeType.split("/")[0]),config.type||(config.type="audio")),config}function GetRecorderType(mediaStream,config){var recorder;return(isChrome||isEdge||isOpera)&&(recorder=StereoAudioRecorder),"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&!isChrome&&(recorder=MediaStreamRecorder),"video"===config.type&&(isChrome||isOpera)&&(recorder=WhammyRecorder),"gif"===config.type&&(recorder=GifRecorder),"canvas"===config.type&&(recorder=CanvasRecorder),isMediaRecorderCompatible()&&isChrome&&recorder!==CanvasRecorder&&recorder!==GifRecorder&&"undefined"!=typeof MediaRecorder&&"requestData"in MediaRecorder.prototype&&mediaStream.getVideoTracks().length&&(recorder=MediaStreamRecorder),config.recorderType&&(recorder=config.recorderType),!config.disableLogs&&recorder&&recorder.name&&console.debug("Using recorderType:",recorder.name||recorder.constructor.name),recorder}function MRecordRTC(mediaStream){this.addStream=function(_mediaStream){_mediaStream&&(mediaStream=_mediaStream)},this.mediaType={audio:!0,video:!0},this.startRecording=function(){var recorderType,mediaType=this.mediaType,mimeType=this.mimeType||{audio:null,video:null,gif:null};if("function"!=typeof mediaType.audio&&isMediaRecorderCompatible()&&mediaStream&&mediaStream.getAudioTracks&&mediaStream.getAudioTracks().length&&mediaStream.getVideoTracks().length&&(this.mediaType.audio=!1),mediaType.audio&&(recorderType=null,"function"==typeof mediaType.audio&&(recorderType=mediaType.audio),this.audioRecorder=new RecordRTC(mediaStream,{type:"audio",bufferSize:this.bufferSize,sampleRate:this.sampleRate,numberOfAudioChannels:this.numberOfAudioChannels||2,disableLogs:this.disableLogs,recorderType:recorderType,mimeType:mimeType.audio}),mediaType.video||this.audioRecorder.startRecording()),mediaType.video){recorderType=null,"function"==typeof mediaType.video&&(recorderType=mediaType.video);var newStream=mediaStream;if(isMediaRecorderCompatible()&&mediaType.audio&&"function"==typeof mediaType.audio){var videoTrack=mediaStream.getVideoTracks()[0];navigator.mozGetUserMedia?(newStream=new MediaStream,newStream.addTrack(videoTrack),recorderType&&recorderType===WhammyRecorder&&(recorderType=MediaStreamRecorder)):newStream=new MediaStream([videoTrack])}this.videoRecorder=new RecordRTC(newStream,{type:"video",video:this.video,canvas:this.canvas,frameInterval:this.frameInterval||10,disableLogs:this.disableLogs,recorderType:recorderType,mimeType:mimeType.video}),mediaType.audio||this.videoRecorder.startRecording()}if(mediaType.audio&&mediaType.video){var self=this;self.videoRecorder.initRecorder(function(){self.audioRecorder.initRecorder(function(){self.videoRecorder.startRecording(),self.audioRecorder.startRecording()})})}mediaType.gif&&(recorderType=null,"function"==typeof mediaType.gif&&(recorderType=mediaType.gif),this.gifRecorder=new RecordRTC(mediaStream,{type:"gif",frameRate:this.frameRate||200,quality:this.quality||10,disableLogs:this.disableLogs,recorderType:recorderType,mimeType:mimeType.gif}),this.gifRecorder.startRecording())},this.stopRecording=function(callback){callback=callback||function(){},this.audioRecorder&&this.audioRecorder.stopRecording(function(blobURL){callback(blobURL,"audio")}),this.videoRecorder&&this.videoRecorder.stopRecording(function(blobURL){callback(blobURL,"video")}),this.gifRecorder&&this.gifRecorder.stopRecording(function(blobURL){callback(blobURL,"gif")})},this.getBlob=function(callback){var output={};return this.audioRecorder&&(output.audio=this.audioRecorder.getBlob()),this.videoRecorder&&(output.video=this.videoRecorder.getBlob()),this.gifRecorder&&(output.gif=this.gifRecorder.getBlob()),callback&&callback(output),output},this.getDataURL=function(callback){function getDataURL(blob,callback00){if("undefined"!=typeof Worker){var webWorker=processInWebWorker(function(_blob){postMessage((new FileReaderSync).readAsDataURL(_blob))});webWorker.onmessage=function(event){callback00(event.data)},webWorker.postMessage(blob)}else{var reader=new FileReader;reader.readAsDataURL(blob),reader.onload=function(event){callback00(event.target.result)}}}function processInWebWorker(_function){var url,blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);if("undefined"!=typeof URL)url=URL;else{if("undefined"==typeof webkitURL)throw"Neither URL nor webkitURL detected.";url=webkitURL}return url.revokeObjectURL(blob),worker}this.getBlob(function(blob){getDataURL(blob.audio,function(_audioDataURL){getDataURL(blob.video,function(_videoDataURL){callback({audio:_audioDataURL,video:_videoDataURL})})})})},this.writeToDisk=function(){RecordRTC.writeToDisk({audio:this.audioRecorder,video:this.videoRecorder,gif:this.gifRecorder})},this.save=function(args){args=args||{audio:!0,video:!0,gif:!0},args.audio&&this.audioRecorder&&this.audioRecorder.save("string"==typeof args.audio?args.audio:""),args.video&&this.videoRecorder&&this.videoRecorder.save("string"==typeof args.video?args.video:""),args.gif&&this.gifRecorder&&this.gifRecorder.save("string"==typeof args.gif?args.gif:"")}}function bytesToSize(bytes){var k=1e3,sizes=["Bytes","KB","MB","GB","TB"];if(0===bytes)return"0 Bytes";var i=parseInt(Math.floor(Math.log(bytes)/Math.log(k)),10);return(bytes/Math.pow(k,i)).toPrecision(3)+" "+sizes[i]}function invokeSaveAsDialog(file,fileName){if(!file)throw"Blob object is required.";if(!file.type)try{file.type="video/webm"}catch(e){}var fileExtension=(file.type||"video/webm").split("/")[1];if(fileName&&-1!==fileName.indexOf(".")){var splitted=fileName.split(".");fileName=splitted[0],fileExtension=splitted[1]}var fileFullName=(fileName||Math.round(9999999999*Math.random())+888888888)+"."+fileExtension;if("undefined"!=typeof navigator.msSaveOrOpenBlob)return navigator.msSaveOrOpenBlob(file,fileFullName);if("undefined"!=typeof navigator.msSaveBlob)return navigator.msSaveBlob(file,fileFullName);var hyperlink=document.createElement("a");hyperlink.href=URL.createObjectURL(file),hyperlink.target="_blank",hyperlink.download=fileFullName,navigator.mozGetUserMedia&&(hyperlink.onclick=function(){(document.body||document.documentElement).removeChild(hyperlink)},(document.body||document.documentElement).appendChild(hyperlink));var evt=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!0});hyperlink.dispatchEvent(evt),navigator.mozGetUserMedia||URL.revokeObjectURL(hyperlink.href)}function isMediaRecorderCompatible(){var isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isChrome=!!window.chrome&&!isOpera,isFirefox="undefined"!=typeof window.InstallTrigger;if(isFirefox)return!0;if(!isChrome)return!1;var verOffset,ix,nAgt=(navigator.appVersion,navigator.userAgent),fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10);return isChrome&&(verOffset=nAgt.indexOf("Chrome"),fullVersion=nAgt.substring(verOffset+7)),-1!==(ix=fullVersion.indexOf(";"))&&(fullVersion=fullVersion.substring(0,ix)),-1!==(ix=fullVersion.indexOf(" "))&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),majorVersion>=49}function MediaStreamRecorder(mediaStream,config){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}var self=this;if(config=config||{mimeType:"video/webm"},"audio"===config.type){if(mediaStream.getVideoTracks().length&&mediaStream.getAudioTracks().length){var stream;navigator.mozGetUserMedia?(stream=new MediaStream,stream.addTrack(mediaStream.getAudioTracks()[0])):stream=new MediaStream(mediaStream.getAudioTracks()),mediaStream=stream}config.mimeType&&-1!==config.mimeType.indexOf("audio")||(config.mimeType=isChrome?"audio/webm":"audio/ogg")}this.record=function(){self.blob=null;var recorderHints=config;config.disableLogs||console.log("Passing following config over MediaRecorder API.",recorderHints),mediaRecorder&&(mediaRecorder=null),isChrome&&!isMediaRecorderCompatible()&&(recorderHints="video/vp8"),mediaRecorder=new MediaRecorder(mediaStream,recorderHints),"canRecordMimeType"in mediaRecorder&&mediaRecorder.canRecordMimeType(config.mimeType)===!1&&(config.disableLogs||console.warn("MediaRecorder API seems unable to record mimeType:",config.mimeType)),mediaRecorder.ignoreMutedMedia=config.ignoreMutedMedia||!1,mediaRecorder.ondataavailable=function(e){self.dontFireOnDataAvailableEvent||!e.data||!e.data.size||e.data.size<100||self.blob||(self.blob=config.getNativeBlob?e.data:new Blob([e.data],{type:config.mimeType||"video/webm"}),self.recordingCallback&&(self.recordingCallback(self.blob),self.recordingCallback=null))},mediaRecorder.onerror=function(error){config.disableLogs||("InvalidState"===error.name?console.error("The MediaRecorder is not in a state in which the proposed operation is allowed to be executed."):"OutOfMemory"===error.name?console.error("The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute."):"IllegalStreamModification"===error.name?console.error("A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute."):"OtherRecordingError"===error.name?console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute."):"GenericError"===error.name?console.error("The UA cannot provide the codec or recording option that has been requested.",error):console.error("MediaRecorder Error",error)),"inactive"!==mediaRecorder.state&&"stopped"!==mediaRecorder.state&&mediaRecorder.stop()},mediaRecorder.start(36e5),config.onAudioProcessStarted&&config.onAudioProcessStarted(),config.initCallback&&config.initCallback()},this.stop=function(callback){mediaRecorder&&(this.recordingCallback=callback||function(){},"recording"===mediaRecorder.state&&(mediaRecorder.requestData(),mediaRecorder.stop()))},this.pause=function(){mediaRecorder&&"recording"===mediaRecorder.state&&mediaRecorder.pause()},this.resume=function(){if(this.dontFireOnDataAvailableEvent){this.dontFireOnDataAvailableEvent=!1;var disableLogs=config.disableLogs;return config.disableLogs=!0,this.record(),void(config.disableLogs=disableLogs)}mediaRecorder&&"paused"===mediaRecorder.state&&mediaRecorder.resume()},this.clearRecordedData=function(){mediaRecorder&&(this.pause(),this.dontFireOnDataAvailableEvent=!0,this.stop())};var mediaRecorder,self=this;!function looper(){return mediaRecorder?isMediaStreamActive()===!1?void self.stop():void setTimeout(looper,1e3):void 0}()}function StereoAudioRecorder(mediaStream,config){function isMediaStreamActive(){if("active"in mediaStream){if(!mediaStream.active)return!1}else if("ended"in mediaStream&&mediaStream.ended)return!1;return!0}function mergeLeftRightBuffers(config,callback){function mergeAudioBuffers(config,cb){function mergeBuffers(channelBuffer,rLength){for(var result=new Float64Array(rLength),offset=0,lng=channelBuffer.length,i=0;lng>i;i++){var buffer=channelBuffer[i];result.set(buffer,offset),offset+=buffer.length}return result}function interleave(leftChannel,rightChannel){for(var length=leftChannel.length+rightChannel.length,result=new Float64Array(length),inputIndex=0,index=0;length>index;)result[index++]=leftChannel[inputIndex],result[index++]=rightChannel[inputIndex],inputIndex++;return result}function writeUTFBytes(view,offset,string){for(var lng=string.length,i=0;lng>i;i++)view.setUint8(offset+i,string.charCodeAt(i))}var numberOfAudioChannels=config.numberOfAudioChannels,leftBuffers=config.leftBuffers.slice(0),rightBuffers=config.rightBuffers.slice(0),sampleRate=config.sampleRate,internalInterleavedLength=config.internalInterleavedLength;2===numberOfAudioChannels&&(leftBuffers=mergeBuffers(leftBuffers,internalInterleavedLength),rightBuffers=mergeBuffers(rightBuffers,internalInterleavedLength)),1===numberOfAudioChannels&&(leftBuffers=mergeBuffers(leftBuffers,internalInterleavedLength));var interleaved;2===numberOfAudioChannels&&(interleaved=interleave(leftBuffers,rightBuffers)),1===numberOfAudioChannels&&(interleaved=leftBuffers);var interleavedLength=interleaved.length,resultingBufferLength=44+2*interleavedLength,buffer=new ArrayBuffer(resultingBufferLength),view=new DataView(buffer);writeUTFBytes(view,0,"RIFF"),view.setUint32(4,44+2*interleavedLength,!0),writeUTFBytes(view,8,"WAVE"),writeUTFBytes(view,12,"fmt "),view.setUint32(16,16,!0),view.setUint16(20,1,!0),view.setUint16(22,numberOfAudioChannels,!0),view.setUint32(24,sampleRate,!0),view.setUint32(28,2*sampleRate,!0),view.setUint16(32,2*numberOfAudioChannels,!0),view.setUint16(34,16,!0),writeUTFBytes(view,36,"data"),view.setUint32(40,2*interleavedLength,!0);for(var lng=interleavedLength,index=44,volume=1,i=0;lng>i;i++)view.setInt16(index,interleaved[i]*(32767*volume),!0),index+=2;return cb?cb({buffer:buffer,view:view}):void postMessage({buffer:buffer,view:view})}if(!isChrome)return void mergeAudioBuffers(config,function(data){callback(data.buffer,data.view)});var webWorker=processInWebWorker(mergeAudioBuffers);webWorker.onmessage=function(event){callback(event.data.buffer,event.data.view),URL.revokeObjectURL(webWorker.workerURL)},webWorker.postMessage(config)}function processInWebWorker(_function){var workerURL=URL.createObjectURL(new Blob([_function.toString(),";this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(workerURL);return worker.workerURL=workerURL,worker}function onAudioProcessDataAvailable(e){if(!isPaused){if(isMediaStreamActive()===!1&&(config.disableLogs||console.error("MediaStream seems stopped."),jsAudioNode.disconnect(),recording=!1),!recording)return void audioInput.disconnect();isAudioProcessStarted||(isAudioProcessStarted=!0,config.onAudioProcessStarted&&config.onAudioProcessStarted(),config.initCallback&&config.initCallback());var left=e.inputBuffer.getChannelData(0);if(leftchannel.push(new Float32Array(left)),2===numberOfAudioChannels){var right=e.inputBuffer.getChannelData(1);rightchannel.push(new Float32Array(right))}recordingLength+=bufferSize}}if(!mediaStream.getAudioTracks().length)throw"Your stream has no audio tracks.";config=config||{};var jsAudioNode,self=this,leftchannel=[],rightchannel=[],recording=!1,recordingLength=0,numberOfAudioChannels=2;config.leftChannel===!0&&(numberOfAudioChannels=1),1===config.numberOfAudioChannels&&(numberOfAudioChannels=1),config.disableLogs||console.debug("StereoAudioRecorder is set to record number of channels: ",numberOfAudioChannels),this.record=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";leftchannel.length=rightchannel.length=0,recordingLength=0,audioInput&&audioInput.connect(jsAudioNode),isAudioProcessStarted=isPaused=!1,recording=!0},this.stop=function(callback){recording=!1,mergeLeftRightBuffers({sampleRate:sampleRate,numberOfAudioChannels:numberOfAudioChannels,internalInterleavedLength:recordingLength,leftBuffers:leftchannel,rightBuffers:1===numberOfAudioChannels?[]:rightchannel},function(buffer,view){self.blob=new Blob([view],{type:"audio/wav"}),self.buffer=new ArrayBuffer(view),self.view=view,self.sampleRate=sampleRate,self.bufferSize=bufferSize,self.length=recordingLength,callback&&callback(),isAudioProcessStarted=!1})},Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext);var context=Storage.AudioContextConstructor,audioInput=context.createMediaStreamSource(mediaStream),legalBufferValues=[0,256,512,1024,2048,4096,8192,16384],bufferSize="undefined"==typeof config.bufferSize?4096:config.bufferSize;if(-1===legalBufferValues.indexOf(bufferSize)&&(config.disableLogs||console.warn("Legal values for buffer-size are "+JSON.stringify(legalBufferValues,null," "))),context.createJavaScriptNode)jsAudioNode=context.createJavaScriptNode(bufferSize,numberOfAudioChannels,numberOfAudioChannels);else{if(!context.createScriptProcessor)throw"WebAudio API has no support on this browser.";jsAudioNode=context.createScriptProcessor(bufferSize,numberOfAudioChannels,numberOfAudioChannels)}audioInput.connect(jsAudioNode),config.bufferSize||(bufferSize=jsAudioNode.bufferSize);var sampleRate="undefined"!=typeof config.sampleRate?config.sampleRate:context.sampleRate||44100;(22050>sampleRate||sampleRate>96e3)&&(config.disableLogs||console.warn("sample-rate must be under range 22050 and 96000.")),config.disableLogs||(console.log("sample-rate",sampleRate),console.log("buffer-size",bufferSize));var isPaused=!1;this.pause=function(){isPaused=!0},this.resume=function(){if(isMediaStreamActive()===!1)throw"Please make sure MediaStream is active.";return recording?void(isPaused=!1):(config.disableLogs||console.info("Seems recording has been restarted."),void this.record())},this.clearRecordedData=function(){this.pause(),leftchannel.length=rightchannel.length=0,recordingLength=0};var isAudioProcessStarted=!1;jsAudioNode.onaudioprocess=onAudioProcessDataAvailable,jsAudioNode.connect(context.destination)}function CanvasRecorder(htmlElement,config){function cloneCanvas(){var newCanvas=document.createElement("canvas"),context=newCanvas.getContext("2d");return newCanvas.width=htmlElement.width,newCanvas.height=htmlElement.height,context.drawImage(htmlElement,0,0),newCanvas}function drawCanvasFrame(){if(isPausedRecording)return lastTime=(new Date).getTime(),setTimeout(drawCanvasFrame,500);if("canvas"===htmlElement.nodeName.toLowerCase()){var duration=(new Date).getTime()-lastTime;return lastTime=(new Date).getTime(),whammy.frames.push({image:cloneCanvas(),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))}html2canvas(htmlElement,{grabMouse:"undefined"==typeof config.showMousePointer||config.showMousePointer,onrendered:function(canvas){var duration=(new Date).getTime()-lastTime;return duration?(lastTime=(new Date).getTime(),whammy.frames.push({image:canvas.toDataURL("image/webp",1),duration:duration}),void(isRecording&&setTimeout(drawCanvasFrame,config.frameInterval))):setTimeout(drawCanvasFrame,config.frameInterval)}})}if("undefined"==typeof html2canvas&&"canvas"!==htmlElement.nodeName.toLowerCase())throw"Please link: //cdn.webrtc-experiment.com/screenshot.js";config=config||{},config.frameInterval||(config.frameInterval=10);var isCanvasSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0)}),(window.webkitRTCPeerConnection||window.webkitGetUserMedia)&&(isCanvasSupportsStreamCapturing=!1);var globalCanvas,globalContext,mediaStreamRecorder;isCanvasSupportsStreamCapturing?(config.disableLogs||console.debug("Your browser supports both MediRecorder API and canvas.captureStream!"),globalCanvas=document.createElement("canvas"),globalCanvas.width=htmlElement.clientWidth||window.innerWidth,globalCanvas.height=htmlElement.clientHeight||window.innerHeight,globalCanvas.style="top: -9999999; left: -99999999; visibility:hidden; position:absoluted; display: none;",(document.body||document.documentElement).appendChild(globalCanvas),globalContext=globalCanvas.getContext("2d")):navigator.mozGetUserMedia&&(config.disableLogs||alert("Canvas recording is NOT supported in Firefox."));var isRecording;this.record=function(){if(isRecording=!0,isCanvasSupportsStreamCapturing){var canvasMediaStream;"captureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"mozCaptureStream"in globalCanvas?canvasMediaStream=globalCanvas.captureStream(25):"webkitCaptureStream"in globalCanvas&&(canvasMediaStream=globalCanvas.captureStream(25));try{var mdStream=new MediaStream;mdStream.addTrack(canvasMediaStream.getVideoTracks()[0]),canvasMediaStream=mdStream}catch(e){}if(!canvasMediaStream)throw"captureStream API are NOT available.";mediaStreamRecorder=new MediaStreamRecorder(canvasMediaStream,{mimeType:"video/webm"}),mediaStreamRecorder.record()}else whammy.frames=[],lastTime=(new Date).getTime(),drawCanvasFrame();config.initCallback&&config.initCallback()},this.getWebPImages=function(callback){if("canvas"!==htmlElement.nodeName.toLowerCase())return void callback();var framesLength=whammy.frames.length;whammy.frames.forEach(function(frame,idx){var framesRemaining=framesLength-idx;document.title=framesRemaining+"/"+framesLength+" frames remaining",config.onEncodingCallback&&config.onEncodingCallback(framesRemaining,framesLength);var webp=frame.image.toDataURL("image/webp",1);whammy.frames[idx].image=webp}),document.title="Generating WebM",callback()},this.stop=function(callback){isRecording=!1;var that=this;if(isCanvasSupportsStreamCapturing&&mediaStreamRecorder){return void mediaStreamRecorder.stop(function(){for(var prop in mediaStreamRecorder)self[prop]=mediaStreamRecorder[prop];callback&&callback(that.blob)})}this.getWebPImages(function(){whammy.compile(function(blob){document.title="Recording finished!",that.blob=blob,that.blob.forEach&&(that.blob=new Blob([],{type:"video/webm"})),callback&&callback(that.blob),whammy.frames=[]})})};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){this.pause(),whammy.frames=[]};var lastTime=(new Date).getTime(),whammy=new Whammy.Video(100)}function WhammyRecorder(mediaStream,config){function drawFrames(frameInterval){frameInterval="undefined"!=typeof frameInterval?frameInterval:10;var duration=(new Date).getTime()-lastTime;return duration?isPausedRecording?(lastTime=(new Date).getTime(),setTimeout(drawFrames,100)):(lastTime=(new Date).getTime(),video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),whammy.frames.push({duration:duration,image:canvas.toDataURL("image/webp")}),void(isStopDrawing||setTimeout(drawFrames,frameInterval,frameInterval))):setTimeout(drawFrames,frameInterval,frameInterval)}function asyncLoop(o){var i=-1,length=o.length,loop=function(){return i++,i===length?void o.callback():void o.functionToLoop(loop,i)};loop()}function dropBlackFrames(_frames,_framesToCheck,_pixTolerance,_frameTolerance,callback){var localCanvas=document.createElement("canvas");localCanvas.width=canvas.width,localCanvas.height=canvas.height;var context2d=localCanvas.getContext("2d"),resultFrames=[],checkUntilNotBlack=-1===_framesToCheck,endCheckFrame=_framesToCheck&&_framesToCheck>0&&_framesToCheck<=_frames.length?_framesToCheck:_frames.length,sampleColor={r:0,g:0,b:0},maxColorDifference=Math.sqrt(Math.pow(255,2)+Math.pow(255,2)+Math.pow(255,2)),pixTolerance=_pixTolerance&&_pixTolerance>=0&&1>=_pixTolerance?_pixTolerance:0,frameTolerance=_frameTolerance&&_frameTolerance>=0&&1>=_frameTolerance?_frameTolerance:0,doNotCheckNext=!1;asyncLoop({length:endCheckFrame,functionToLoop:function(loop,f){var matchPixCount,endPixCheck,maxPixCount,finishImage=function(){!doNotCheckNext&&maxPixCount*frameTolerance>=maxPixCount-matchPixCount||(checkUntilNotBlack&&(doNotCheckNext=!0),resultFrames.push(_frames[f])),loop()};if(doNotCheckNext)finishImage();else{var image=new Image;image.onload=function(){context2d.drawImage(image,0,0,canvas.width,canvas.height);var imageData=context2d.getImageData(0,0,canvas.width,canvas.height);matchPixCount=0,endPixCheck=imageData.data.length,maxPixCount=imageData.data.length/4;for(var pix=0;endPixCheck>pix;pix+=4){var currentColor={r:imageData.data[pix],g:imageData.data[pix+1],b:imageData.data[pix+2]},colorDifference=Math.sqrt(Math.pow(currentColor.r-sampleColor.r,2)+Math.pow(currentColor.g-sampleColor.g,2)+Math.pow(currentColor.b-sampleColor.b,2));maxColorDifference*pixTolerance>=colorDifference&&matchPixCount++}finishImage()},image.src=_frames[f].image}},callback:function(){resultFrames=resultFrames.concat(_frames.slice(endCheckFrame)),resultFrames.length<=0&&resultFrames.push(_frames[_frames.length-1]),callback(resultFrames)}})}config=config||{},config.frameInterval||(config.frameInterval=10),config.disableLogs||console.log("Using frames-interval:",config.frameInterval),this.record=function(){config.width||(config.width=320),config.height||(config.height=240),config.video||(config.video={width:config.width,height:config.height}),config.canvas||(config.canvas={width:config.width,height:config.height}),canvas.width=config.canvas.width,canvas.height=config.canvas.height,context=canvas.getContext("2d"),config.video&&config.video instanceof HTMLVideoElement?(video=config.video.cloneNode(),config.initCallback&&config.initCallback()):(video=document.createElement("video"),"undefined"!=typeof video.srcObject?video.srcObject=mediaStream:video.src=URL.createObjectURL(mediaStream),video.onloadedmetadata=function(){config.initCallback&&config.initCallback()},video.width=config.video.width,video.height=config.video.height),video.muted=!0,video.play(),lastTime=(new Date).getTime(),whammy=new Whammy.Video,config.disableLogs||(console.log("canvas resolutions",canvas.width,"*",canvas.height),console.log("video width/height",video.width||canvas.width,"*",video.height||canvas.height)),drawFrames(config.frameInterval)};var isStopDrawing=!1;this.stop=function(callback){isStopDrawing=!0;var _this=this;setTimeout(function(){dropBlackFrames(whammy.frames,-1,null,null,function(frames){whammy.frames=frames,config.advertisement&&config.advertisement.length&&(whammy.frames=config.advertisement.concat(whammy.frames)),whammy.compile(function(blob){_this.blob=blob,_this.blob.forEach&&(_this.blob=new Blob([],{type:"video/webm"})),callback&&callback(_this.blob)})})},10)};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){this.pause(),whammy.frames=[]};var video,lastTime,whammy,canvas=document.createElement("canvas"),context=canvas.getContext("2d")}function GifRecorder(mediaStream,config){if("undefined"==typeof GIFEncoder)throw"Please link: https://cdn.webrtc-experiment.com/gif-recorder.js";config=config||{};var isHTMLObject=mediaStream instanceof CanvasRenderingContext2D||mediaStream instanceof HTMLCanvasElement; +this.record=function(){function drawVideoFrame(time){return isPausedRecording?setTimeout(function(){drawVideoFrame(time)},100):(lastAnimationFrame=requestAnimationFrame(drawVideoFrame),void 0===typeof lastFrameTime&&(lastFrameTime=time),void(90>time-lastFrameTime||(!isHTMLObject&&video.paused&&video.play(),context.drawImage(video,0,0,canvas.width,canvas.height),config.onGifPreview&&config.onGifPreview(canvas.toDataURL("image/png")),gifEncoder.addFrame(context),lastFrameTime=time)))}isHTMLObject||(config.width||(config.width=video.offsetWidth||320),this.height||(config.height=video.offsetHeight||240),config.video||(config.video={width:config.width,height:config.height}),config.canvas||(config.canvas={width:config.width,height:config.height}),canvas.width=config.canvas.width,canvas.height=config.canvas.height,video.width=config.video.width,video.height=config.video.height),gifEncoder=new GIFEncoder,gifEncoder.setRepeat(0),gifEncoder.setDelay(config.frameRate||200),gifEncoder.setQuality(config.quality||10),gifEncoder.start(),startTime=Date.now();lastAnimationFrame=requestAnimationFrame(drawVideoFrame),config.initCallback&&config.initCallback()},this.stop=function(){lastAnimationFrame&&cancelAnimationFrame(lastAnimationFrame),endTime=Date.now(),this.blob=new Blob([new Uint8Array(gifEncoder.stream().bin)],{type:"image/gif"}),gifEncoder.stream().bin=[]};var isPausedRecording=!1;this.pause=function(){isPausedRecording=!0},this.resume=function(){isPausedRecording=!1},this.clearRecordedData=function(){gifEncoder&&(this.pause(),gifEncoder.stream().bin=[])};var canvas=document.createElement("canvas"),context=canvas.getContext("2d");if(isHTMLObject&&(mediaStream instanceof CanvasRenderingContext2D?context=mediaStream:mediaStream instanceof HTMLCanvasElement&&(context=mediaStream.getContext("2d"))),!isHTMLObject){var video=document.createElement("video");video.muted=!0,video.autoplay=!0,"undefined"!=typeof video.srcObject?video.srcObject=mediaStream:video.src=URL.createObjectURL(mediaStream),video.play()}var startTime,endTime,lastFrameTime,gifEncoder,lastAnimationFrame=null}RecordRTC.getFromDisk=function(type,callback){if(!callback)throw"callback is mandatory.";console.log("Getting recorded "+("all"===type?"blobs":type+" blob ")+" from disk!"),DiskStorage.Fetch(function(dataURL,_type){"all"!==type&&_type===type+"Blob"&&callback&&callback(dataURL),"all"===type&&callback&&callback(dataURL,_type.replace("Blob",""))})},RecordRTC.writeToDisk=function(options){console.log("Writing recorded blob(s) to disk!"),options=options||{},options.audio&&options.video&&options.gif?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL,gifBlob:gifDataURL})})})}):options.audio&&options.video?options.audio.getDataURL(function(audioDataURL){options.video.getDataURL(function(videoDataURL){DiskStorage.Store({audioBlob:audioDataURL,videoBlob:videoDataURL})})}):options.audio&&options.gif?options.audio.getDataURL(function(audioDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({audioBlob:audioDataURL,gifBlob:gifDataURL})})}):options.video&&options.gif?options.video.getDataURL(function(videoDataURL){options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({videoBlob:videoDataURL,gifBlob:gifDataURL})})}):options.audio?options.audio.getDataURL(function(audioDataURL){DiskStorage.Store({audioBlob:audioDataURL})}):options.video?options.video.getDataURL(function(videoDataURL){DiskStorage.Store({videoBlob:videoDataURL})}):options.gif&&options.gif.getDataURL(function(gifDataURL){DiskStorage.Store({gifBlob:gifDataURL})})},"undefined"!=typeof module&&(module.exports=RecordRTC),"function"==typeof define&&define.amd&&define("RecordRTC",[],function(){return RecordRTC}),MRecordRTC.getFromDisk=RecordRTC.getFromDisk,MRecordRTC.writeToDisk=RecordRTC.writeToDisk;var requestAnimationFrame=window.requestAnimationFrame;"undefined"==typeof requestAnimationFrame&&("undefined"!=typeof webkitRequestAnimationFrame&&(requestAnimationFrame=webkitRequestAnimationFrame),"undefined"!=typeof mozRequestAnimationFrame&&(requestAnimationFrame=mozRequestAnimationFrame));var cancelAnimationFrame=window.cancelAnimationFrame;"undefined"==typeof cancelAnimationFrame&&("undefined"!=typeof webkitCancelAnimationFrame&&(cancelAnimationFrame=webkitCancelAnimationFrame),"undefined"!=typeof mozCancelAnimationFrame&&(cancelAnimationFrame=mozCancelAnimationFrame));var AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;if("undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"==typeof navigator)throw'Please make sure to define a global variable named as "navigator"';"undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia);var isEdge=!(-1===navigator.userAgent.indexOf("Edge")||!navigator.msSaveBlob&&!navigator.msSaveOrOpenBlob),isOpera=!!window.opera||-1!==navigator.userAgent.indexOf("OPR/"),isChrome=!isOpera&&!isEdge&&!!navigator.webkitGetUserMedia,MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("getVideoTracks"in MediaStream.prototype||(MediaStream.prototype.getVideoTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("video")&&tracks.push(track)}),tracks},MediaStream.prototype.getAudioTracks=function(){if(!this.getTracks)return[];var tracks=[];return this.getTracks.forEach(function(track){-1!==track.kind.toString().indexOf("audio")&&tracks.push(track)}),tracks}),"stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getAudioTracks().forEach(function(track){track.stop&&track.stop()}),this.getVideoTracks().forEach(function(track){track.stop&&track.stop()})})),"undefined"!=typeof location&&0===location.href.indexOf("file:")&&console.error("Please load this HTML file on HTTP or HTTPS.");var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext);var Whammy=function(){function WhammyVideo(duration){this.frames=[],this.duration=duration||1,this.quality=.8}function processInWebWorker(_function){var blob=URL.createObjectURL(new Blob([_function.toString(),"this.onmessage = function (e) {"+_function.name+"(e.data);}"],{type:"application/javascript"})),worker=new Worker(blob);return URL.revokeObjectURL(blob),worker}function whammyInWebWorker(frames){function ArrayToWebM(frames){var info=checkFrames(frames);if(!info)return[];for(var clusterMaxDuration=3e4,EBML=[{id:440786851,data:[{data:1,id:17030},{data:1,id:17143},{data:4,id:17138},{data:8,id:17139},{data:"webm",id:17026},{data:2,id:17031},{data:2,id:17029}]},{id:408125543,data:[{id:357149030,data:[{data:1e6,id:2807729},{data:"whammy",id:19840},{data:"whammy",id:22337},{data:doubleToString(info.duration),id:17545}]},{id:374648427,data:[{id:174,data:[{data:1,id:215},{data:1,id:29637},{data:0,id:156},{data:"und",id:2274716},{data:"V_VP8",id:134},{data:"VP8",id:2459272},{data:1,id:131},{id:224,data:[{data:info.width,id:176},{data:info.height,id:186}]}]}]}]}],frameNumber=0,clusterTimecode=0;frameNumberclusterDuration);var clusterCounter=0,cluster={id:524531317,data:getClusterData(clusterTimecode,clusterCounter,clusterFrames)};EBML[1].data.push(cluster),clusterTimecode+=clusterDuration}return generateEBML(EBML)}function getClusterData(clusterTimecode,clusterCounter,clusterFrames){return[{data:clusterTimecode,id:231}].concat(clusterFrames.map(function(webp){var block=makeSimpleBlock({discardable:0,frame:webp.data.slice(4),invisible:0,keyframe:1,lacing:0,trackNum:1,timecode:Math.round(clusterCounter)});return clusterCounter+=webp.duration,{data:block,id:163}}))}function checkFrames(frames){if(!frames[0])return void postMessage({error:"Something went wrong. Maybe WebP format is not supported in the current browser."});for(var width=frames[0].width,height=frames[0].height,duration=frames[0].duration,i=1;i0;)parts.push(255&num),num>>=8;return new Uint8Array(parts.reverse())}function strToBuffer(str){return new Uint8Array(str.split("").map(function(e){return e.charCodeAt(0)}))}function bitsToBuffer(bits){var data=[],pad=bits.length%8?new Array(9-bits.length%8).join("0"):"";bits=pad+bits;for(var i=0;i127)throw"TrackNumber > 127 not supported";var out=[128|data.trackNum,data.timecode>>8,255&data.timecode,flags].map(function(e){return String.fromCharCode(e)}).join("")+data.frame;return out}function parseWebP(riff){for(var VP8=riff.RIFF[0].WEBP[0],frameStart=VP8.indexOf("*"),i=0,c=[];4>i;i++)c[i]=VP8.charCodeAt(frameStart+3+i);var width,height,tmp;return tmp=c[1]<<8|c[0],width=16383&tmp,tmp=c[3]<<8|c[2],height=16383&tmp,{width:width,height:height,data:VP8,riff:riff}}function getStrLength(string,offset){return parseInt(string.substr(offset+4,4).split("").map(function(i){var unpadded=i.charCodeAt(0).toString(2);return new Array(8-unpadded.length+1).join("0")+unpadded}).join(""),2)}function parseRIFF(string){for(var offset=0,chunks={};offset= 29 ) [ audio/video in single webm/mp4 container or only audio in ogg ] -// Opera (all versions) [ same as chrome ] -// Android (Chrome) [ only video ] -// Android (Opera) [ only video ] -// Android (Firefox) [ only video ] -// Microsoft Edge (Only Audio & Gif) - -//------------------------------------ +//-------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence -//------------------------------------ -// Note: RecordRTC.js is using 3 other libraries; you need to accept their licences as well. -//------------------------------------ -// 1. RecordRTC.js -// 2. MRecordRTC.js -// 3. Cross-Browser-Declarations.js -// 4. Storage.js -// 5. MediaStreamRecorder.js -// 6. StereoAudioRecorder.js -// 7. CanvasRecorder.js -// 8. WhammyRecorder.js -// 9. Whammy.js -// 10. DiskStorage.js -// 11. GifRecorder.js -//------------------------------------ - -'use strict'; +//-------------------------------------------------- diff --git a/RecordRTC/dev/isMediaRecorderCompatible.js b/RecordRTC/dev/isMediaRecorderCompatible.js new file mode 100644 index 00000000..85881240 --- /dev/null +++ b/RecordRTC/dev/isMediaRecorderCompatible.js @@ -0,0 +1,42 @@ +function isMediaRecorderCompatible() { + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isChrome = !!window.chrome && !isOpera; + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + + if (isFirefox) { + return true; + } + + if (!isChrome) { + return false; + } + + var nVer = navigator.appVersion; + var nAgt = navigator.userAgent; + var fullVersion = '' + parseFloat(navigator.appVersion); + var majorVersion = parseInt(navigator.appVersion, 10); + var nameOffset, verOffset, ix; + + if (isChrome) { + verOffset = nAgt.indexOf('Chrome'); + fullVersion = nAgt.substring(verOffset + 7); + } + + // trim the fullVersion string at semicolon/space if present + if ((ix = fullVersion.indexOf(';')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + if ((ix = fullVersion.indexOf(' ')) !== -1) { + fullVersion = fullVersion.substring(0, ix); + } + + majorVersion = parseInt('' + fullVersion, 10); + + if (isNaN(majorVersion)) { + fullVersion = '' + parseFloat(navigator.appVersion); + majorVersion = parseInt(navigator.appVersion, 10); + } + + return majorVersion >= 49; +} diff --git a/RecordRTC/index.html b/RecordRTC/index.html index b164d468..9cd91f29 100644 --- a/RecordRTC/index.html +++ b/RecordRTC/index.html @@ -84,7 +84,7 @@ - + @@ -94,7 +94,7 @@ -

+

HOME © Muaz Khan . @@ -102,6 +102,12 @@

Github . Latest issues . What's New? + +
+ + + +

@@ -272,7 +278,6 @@

button.mediaCapturedCallback = function() { button.recordRTC = RecordRTC(button.stream, { type: mediaContainerFormat.value === 'Gif' ? 'gif' : 'video', - recorderType: isChrome && mediaContainerFormat.value !== 'Gif' && typeof MediaRecorder !== 'undefined' ? MediaStreamRecorder : null, mimeType: isChrome ? null: mimeType, disableLogs: params.disableLogs || false, canvas: { @@ -284,7 +289,6 @@

button.recordingEndedCallback = function(url) { recordingPlayer.src = null; - recordingPlayer.srcObject = null; if(mediaContainerFormat.value === 'Gif') { recordingPlayer.pause(); @@ -314,7 +318,7 @@

captureAudio(commonConfig); button.mediaCapturedCallback = function() { - button.recordRTC = RecordRTC(button.stream, { + var options = { type: 'audio', mimeType: mimeType, bufferSize: typeof params.bufferSize == 'undefined' ? 0 : parseInt(params.bufferSize), @@ -322,7 +326,13 @@

leftChannel: params.leftChannel || false, disableLogs: params.disableLogs || false, recorderType: webrtcDetectedBrowser === 'edge' ? StereoAudioRecorder : null - }); + }; + + if(typeof params.sampleRate == 'undefined') { + delete options.sampleRate; + } + + button.recordRTC = RecordRTC(button.stream, options); button.recordingEndedCallback = function(url) { var audio = new Audio(); @@ -348,7 +358,7 @@

button.mediaCapturedCallback = function() { - if(webrtcDetectedBrowser !== 'firefox') { // opera or chrome etc. + if(typeof MediaRecorder === 'undefined') { // opera or chrome etc. button.recordRTC = []; if(!params.bufferSize) { @@ -356,18 +366,23 @@

params.bufferSize = 16384; } - var audioRecorder = RecordRTC(button.stream, { + var options = { type: 'audio', bufferSize: typeof params.bufferSize == 'undefined' ? 0 : parseInt(params.bufferSize), sampleRate: typeof params.sampleRate == 'undefined' ? 44100 : parseInt(params.sampleRate), leftChannel: params.leftChannel || false, disableLogs: params.disableLogs || false, recorderType: webrtcDetectedBrowser === 'edge' ? StereoAudioRecorder : null - }); + }; + + if(typeof params.sampleRate == 'undefined') { + delete options.sampleRate; + } + + var audioRecorder = RecordRTC(button.stream, options); var videoRecorder = RecordRTC(button.stream, { type: 'video', - // recorderType: isChrome && typeof MediaRecorder !== 'undefined' ? MediaStreamRecorder : null, disableLogs: params.disableLogs || false, canvas: { width: params.canvas_width || 320, @@ -409,13 +424,13 @@

type: 'video', mimeType: mimeType, disableLogs: params.disableLogs || false, - // we can't pass bitrates or framerates here - // Firefox MediaRecorder API lakes these features + // bitsPerSecond: 25 * 8 * 1025 // 25 kbits/s + getNativeBlob: false // enable it for longer recordings }); button.recordingEndedCallback = function(url) { - recordingPlayer.srcObject = null; recordingPlayer.muted = false; + recordingPlayer.removeAttribute('muted'); recordingPlayer.src = url; recordingPlayer.play(); @@ -436,7 +451,6 @@

button.recordRTC = RecordRTC(button.stream, { type: mediaContainerFormat.value === 'Gif' ? 'gif' : 'video', mimeType: mimeType, - recorderType: isChrome && mediaContainerFormat.value !== 'Gif' && typeof MediaRecorder !== 'undefined' ? MediaStreamRecorder : null, disableLogs: params.disableLogs || false, canvas: { width: params.canvas_width || 320, @@ -446,7 +460,6 @@

button.recordingEndedCallback = function(url) { recordingPlayer.src = null; - recordingPlayer.srcObject = null; if(mediaContainerFormat.value === 'Gif') { recordingPlayer.pause(); @@ -466,6 +479,8 @@

}; } + // note: audio+tab is supported in Chrome 50+ + // todo: add audio+tab recording if(recordingMedia.value === 'record-audio-plus-screen') { captureAudioPlusScreen(commonConfig); @@ -474,13 +489,13 @@

type: 'video', mimeType: mimeType, disableLogs: params.disableLogs || false, - // we can't pass bitrates or framerates here - // Firefox MediaRecorder API lakes these features + // bitsPerSecond: 25 * 8 * 1025 // 25 kbits/s + getNativeBlob: false // enable it for longer recordings }); button.recordingEndedCallback = function(url) { - recordingPlayer.srcObject = null; recordingPlayer.muted = false; + recordingPlayer.removeAttribute('muted'); recordingPlayer.src = url; recordingPlayer.play(); @@ -526,7 +541,12 @@

} function captureAudioPlusVideo(config) { - captureUserMedia({video: true, audio: true}, function(audioVideoStream) { + captureUserMedia({video: { + mandatory: { + minWidth: 1920, + minHeight: 1080 + } + }, audio: true}, function(audioVideoStream) { recordingPlayer.srcObject = audioVideoStream; recordingPlayer.play(); @@ -559,6 +579,7 @@

return; } + delete screenConstraints.video.mozMediaSource; captureUserMedia(screenConstraints, function(screenStream) { recordingPlayer.srcObject = screenStream; recordingPlayer.play(); @@ -595,6 +616,7 @@

screenConstraints.audio = true; + delete screenConstraints.video.mozMediaSource; captureUserMedia(screenConstraints, function(screenStream) { recordingPlayer.srcObject = screenStream; recordingPlayer.play(); @@ -680,7 +702,10 @@

if(webrtcDetectedBrowser === 'chrome') { recordingMedia.innerHTML = '' + recordingMedia.innerHTML; - console.info('This RecordRTC demo merely tries to playback recorded audio/video sync inside the browser. It still generates two separate files (WAV/WebM).'); + + if(typeof MediaRecorder === 'undefined') { + console.info('This RecordRTC demo merely tries to playback recorded audio/video sync inside the browser. It still generates two separate files (WAV/WebM).'); + } } function saveToDiskOrOpenNewTab(recordRTC) { @@ -732,6 +757,10 @@

Canvas Recording! (Web-Page Recording) +
  • + Record Canvas2D Drawings (Huge collection of 2D drawings!) +
  • +
  • Record Mp3 or Wav (Pre-recorded audio) i.e. (Audio on Demand)
  • diff --git a/RecordRTC/package.json b/RecordRTC/package.json index 8681fefb..03c359de 100644 --- a/RecordRTC/package.json +++ b/RecordRTC/package.json @@ -1,7 +1,7 @@ { "name": "recordrtc", "preferGlobal": false, - "version": "5.1.3", + "version": "5.3.0", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", @@ -35,15 +35,13 @@ "_id": "recordrtc@", "_from": "recordrtc@", "devDependencies": { - "grunt": "latest", - "grunt-cli": "latest", - "load-grunt-tasks": "latest", - "grunt-contrib-concat": "latest", - "grunt-contrib-csslint": "latest", - "grunt-contrib-jshint": "latest", - "grunt-contrib-uglify": "latest", - "grunt-htmlhint": "latest", - "grunt-jsbeautifier": "latest", - "grunt-bump": "latest" + "grunt": "0.4.5", + "grunt-cli": "0.1.13", + "load-grunt-tasks": "3.4.0", + "grunt-contrib-concat": "0.5.1", + "grunt-contrib-jshint": "0.11.3", + "grunt-contrib-uglify": "0.11.0", + "grunt-jsbeautifier": "0.2.10", + "grunt-bump": "0.7.0" } } diff --git a/demos/remote-stream-recording.html b/demos/remote-stream-recording.html index f0515ea5..8fdc8fdc 100644 --- a/demos/remote-stream-recording.html +++ b/demos/remote-stream-recording.html @@ -28,6 +28,13 @@

    Recording Remote Audio Streams / RecordRTC

    +
    + Remote audio+video recording is supported in Chrome version >= 49. You have to enable MediaRecorder API via chrome://flags/#enable-experimental-web-platform-features + +

    + And test this demo instead: RecordRTC-and-RTCMultiConnection.html +
    +

    issue: unable to record remote audio streams using RecordRTC.

      diff --git a/docs/WebRTC-PeerConnection.html b/docs/WebRTC-PeerConnection.html index 33bd6b17..538574af 100644 --- a/docs/WebRTC-PeerConnection.html +++ b/docs/WebRTC-PeerConnection.html @@ -13,7 +13,7 @@ - + @@ -124,7 +124,7 @@

      WebRTC PeerConnection API

      connection.createOffer(getOfferSDP, onfailure, sdpConstraints); function getOfferSDP(offerSDP) { - connection.setLocalDescription(offerSDP); + connection.setLocalDescription(offerSDP [, successCallback, failureCallback]); console.log('offer sdp', offerSDP.sdp); console.log('type', offerSDP.type); @@ -143,7 +143,7 @@

      WebRTC PeerConnection API

      // "setRemoteDescription" is quickly called for answerer var remoteSessionDescription = new RTCSessionDescription(offerSDP); -connection.setRemoteDescription(remoteSessionDescription); +connection.setRemoteDescription(remoteSessionDescription, successCallback, failureCallback); connection.createAnswer(getAnswerSDP, onfailure, sdpConstraints); function getAnswerSDP(answerSDP) { @@ -163,7 +163,7 @@

      WebRTC PeerConnection API

      // it will complete the offer/answer handshake var remoteSessionDescription = new RTCSessionDescription(answerSDP); -connection.setRemoteDescription(remoteSessionDescription); +connection.setRemoteDescription(remoteSessionDescription, successCallback, failureCallback); @@ -211,17 +211,14 @@

      A working example with WebSockets

      -var isChrome = !!navigator.webkitGetUserMedia;
      -
       var STUN = {
      -    url: isChrome 
      -       ? 'stun:stun.l.google.com:19302' 
      -       : 'stun:23.21.150.121'
      +    urls: 'stun:stun.l.google.com:19302'
       };
       
       var TURN = {
      -    url: 'turn:homeo@turn.bistri.com:80',
      -    credential: 'homeo'
      +    urls: 'turn:turn.bistri.com:80',
      +    credential: 'homeo',
      +    username: 'homeo'
       };
       
       var iceServers = {
      @@ -291,6 +288,9 @@ 

      A working example with WebSockets

      +// it is suggested to use gumadapter.js instead:
      +// https://github.com/muaz-khan/gumadapter
      +
       navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError);
       var MediaConstraints = {
           audio: true,
      @@ -332,6 +332,8 @@ 

      A working example with WebSockets

      button.onclick = function() { this.disabled = true; + // it is suggested to use gumadapter.js instead: + // https://github.com/muaz-khan/gumadapter navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError); var MediaConstraints = { audio: true, @@ -362,7 +364,7 @@

      A working example with WebSockets

      }; peer.createOffer(function(offerSDP) { - peer.setLocalDescription(offerSDP); + peer.setLocalDescription(offerSDP, successCallback, failureCallback); socket.send({ targetUser: 'target-user-id', offerSDP: offerSDP @@ -371,17 +373,14 @@

      A working example with WebSockets

      } }; -var isChrome = !!navigator.webkitGetUserMedia; - var STUN = { - url: isChrome - ? 'stun:stun.l.google.com:19302' - : 'stun:23.21.150.121' + urls: 'stun:stun.l.google.com:19302' }; var TURN = { - url: 'turn:homeo@turn.bistri.com:80', - credential: 'homeo' + urls: 'turn:turn.bistri.com:80', + credential: 'homeo', + username: 'homeo' }; var iceServers = { @@ -418,6 +417,8 @@

      A working example with WebSockets

      } }; function createAnswer(offerSDP) { + // it is suggested to use gumadapter.js instead: + // https://github.com/muaz-khan/gumadapter navigator.webkitGetUserMedia(MediaConstraints, OnMediaSuccess, OnMediaError); var MediaConstraints = { audio: true, @@ -450,10 +451,10 @@

      A working example with WebSockets

      // remote-descriptions should be set earlier // using offer-sdp provided by the offerer var remoteDescription = new RTCSessionDescription(offerSDP); - peer.setRemoteDescription(remoteDescription); + peer.setRemoteDescription(remoteDescription, successCallback, failureCallback); peer.createAnswer(function(answerSDP) { - peer.setLocalDescription(answerSDP); + peer.setLocalDescription(answerSDP, successCallback, failureCallback); socket.send({ targetUser: 'target-user-id', answerSDP: answerSDP @@ -473,7 +474,7 @@

      A working example with WebSockets

      if(data.targetUser !== self && data.answerSDP) { // completing the handshake; this code is for offerer var remoteDescription = new RTCSessionDescription(answerSDP); - peer.setRemoteDescription(remoteDescription); + peer.setRemoteDescription(remoteDescription, successCallback, failureCallback); } };
      @@ -505,7 +506,7 @@

      A working example with WebSockets

      peer.addIceCandidate(new [moz]RTCIceCandidate({ sdpMLineIndex: sdpMLineIndex, candidate : candidate - })); + }), successCallback, failureCallback); } };
      @@ -580,13 +581,20 @@

      How to use socket.io for signaling?

      sdpConstraints?

      +// for Chrome:
       var sdpConstraints = {
      -        optional: [],
      -        mandatory: {
      -            OfferToReceiveAudio: true,
      -            OfferToReceiveVideo: true
      -        }
      -    };
      +    optional: [],
      +    mandatory: {
      +        OfferToReceiveAudio: true,
      +        OfferToReceiveVideo: true
      +    }
      +};
      +
      +// for Firefox:
      +var sdpConstraints = {
      +    OfferToReceiveAudio: true,
      +    OfferToReceiveVideo: true
      +};
       
      @@ -601,7 +609,7 @@

      Demos? Examples?

      Simplest Demo using socket.io

      -// http://www.webrtc-experiment.com/socket.io/PeerConnection.js
      +// http://cdn.webrtc-experiment.com/socket.io/PeerConnection.js
       
       var offerer = new Feedback

    RecordRTC: WebRTC audio/video recording ® Muaz Khan

    - + diff --git a/docs/how-to-WebRTC-video-conferencing.html b/docs/how-to-WebRTC-video-conferencing.html index 18de0fbd..db959f9c 100644 --- a/docs/how-to-WebRTC-video-conferencing.html +++ b/docs/how-to-WebRTC-video-conferencing.html @@ -15,7 +15,7 @@ - + @@ -100,7 +100,7 @@

    You should

  • uniquely identify the current user
  • -

    It is preferred to use socket.io because it supports multiplexing. WebSockets is useless in conferencing.

    +

    It is preferred to use socket.io because it supports multiplexing.

    Those functions

    @@ -302,6 +302,6 @@

    + diff --git a/experimental/mozCaptureStreamUntilEnded/index.html b/experimental/mozCaptureStreamUntilEnded/index.html index c93b38b4..ab1bf02e 100644 --- a/experimental/mozCaptureStreamUntilEnded/index.html +++ b/experimental/mozCaptureStreamUntilEnded/index.html @@ -9,125 +9,8 @@ + -
    ←HOME - -

    mozCaptureStreamUntilEnded

    - -

    Copyright © 2013 Muaz Khan<@muazkh>.

    -
    -
    -
    +
    +
    +

    + mozCaptureStreamUntilEnded + ® + Muaz Khan +

    +

    + HOME + © + Muaz Khan + + . + @WebRTCWeb + + . + Github + + . + Latest issues + + . + What's New? +

    +
    - +

    WebM video loaded from server


    + +

    @@ -193,11 +96,8 @@

    Answerer-to-Offerer

    + + + \ No newline at end of file diff --git a/ffmpeg/README.md b/ffmpeg/README.md index e2590f24..728103ad 100644 --- a/ffmpeg/README.md +++ b/ffmpeg/README.md @@ -6,6 +6,7 @@ | Transcoding WebM into mp4 | [Demo](https://www.webrtc-experiment.com/ffmpeg/webm-to-mp4.html) | [Source](https://github.com/muaz-khan/Ffmpeg.js/blob/master/webm-to-mp4.html) | | Transcoding WebM into mp4; then merging WAV+mp4 into single mp4 | [Demo](https://www.webrtc-experiment.com/ffmpeg/merging-wav-and-webm-into-mp4.html) | [Source](https://github.com/muaz-khan/Ffmpeg.js/blob/master/merging-wav-and-webm-into-mp4.html) | | Recording Audio+Canvas and merging in single mp4 | [Demo](https://www.webrtc-experiment.com/ffmpeg/audio-plus-canvas-recording.html) | [Source](https://github.com/muaz-khan/Ffmpeg.js/blob/master/audio-plus-canvas-recording.html) | +| Recording Audio+Screen and merging in single mp4 | [Demo](https://www.webrtc-experiment.com/ffmpeg/audio-plus-screen-recording.html) | [Source](https://github.com/muaz-khan/Ffmpeg.js/blob/master/audio-plus-screen-recording.html) | Remember: [`ffmpeg-asm.js`](https://4dbefa02675a4cdb7fc25d009516b060a84a3b4b.googledrive.com/host/0B6GWd_dUUTT8WjhzNlloZmZtdzA/ffmpeg_asm.js)'s credit goes to: https://github.com/bgrins/videoconverter.js diff --git a/ffmpeg/audio-plus-screen-recording.html b/ffmpeg/audio-plus-screen-recording.html new file mode 100644 index 00000000..5875007f --- /dev/null +++ b/ffmpeg/audio-plus-screen-recording.html @@ -0,0 +1,445 @@ + + + + + Record audio+screen and get mp4! ® Muaz Khan + + + + + + + + + + + + + + + + + + + +
    +
    +

    + Record audio+screen and get mp4! +

    +

    + HOME + © + Muaz Khan + . + @WebRTCWeb + + . + Github + + . + Latest issues + + . + What's New? +

    +
    + +
    + +
    +

    Logs

    +
      +
    1. + RecordRTC experiment converting Merging WebM/WAV into mp4 inside the browser! +
    2. + +
    3. + ffmpeg-asm.js is integrated with RecordRTC by Gregory McGee! +
    4. +
    +
    + +
    +

    Record audio/screen and convert/merge into "mp4"!

    + +
    + + +
    + +
    +
    +
    + + +
    +

    Ffmpeg.js Issues +

    +
    +
    + +
    + + +
    +

    Using ffmpeg-asm.js...

    +
      +
    1. + WebM can be converted in mp4. +
    2. +
    3. + WAV can be converted in ogg or mp3. +
    4. +
    5. + WAV can be merged in mp4. +
    6. +
    +

    + If WAV or WebM is 5MB in size; ogg, mp3 or mp4 will be 700kb! +

    +
    + +
    +

    + RecordRTC is MIT licensed on Github! Documentation +

    +
    + +
    +

    Latest Updates

    +
    +
    +
    + + + + + + + + + + + + diff --git a/ffmpeg/merging-wav-and-webm-into-mp4.html b/ffmpeg/merging-wav-and-webm-into-mp4.html index 1f4c1b6d..1e02eb00 100644 --- a/ffmpeg/merging-wav-and-webm-into-mp4.html +++ b/ffmpeg/merging-wav-and-webm-into-mp4.html @@ -1,7 +1,7 @@  @@ -328,6 +328,12 @@

    Record audio/video and convert/merge into "mp4"!

    document.querySelector('#record-video').disabled = false; }; + +
    +

    Ffmpeg.js Issues +

    +
    +

    Feedback

    @@ -380,6 +386,9 @@

    + diff --git a/ffmpeg/wav-to-ogg.html b/ffmpeg/wav-to-ogg.html index 349cc7df..15692ee4 100644 --- a/ffmpeg/wav-to-ogg.html +++ b/ffmpeg/wav-to-ogg.html @@ -1,7 +1,7 @@  @@ -266,6 +266,12 @@

    Record Audio and convert into "Ogg" using +

    Ffmpeg.js Issues +

    +
    +

    Feedback

    @@ -318,6 +324,9 @@

    + diff --git a/ffmpeg/webm-to-mp4.html b/ffmpeg/webm-to-mp4.html index 7ef37612..159ca0ee 100644 --- a/ffmpeg/webm-to-mp4.html +++ b/ffmpeg/webm-to-mp4.html @@ -1,7 +1,7 @@  @@ -254,6 +254,12 @@

    Record Video and convert into "mp4" using +

    Ffmpeg.js Issues +

    +
    +

    Feedback

    @@ -284,7 +290,7 @@

    Using ffmpeg-asm.js...

    - RecordRTC is MIT licensed on Github! Documentation + RecordRTC is MIT licensed on Github! Documentation

    @@ -306,6 +312,9 @@

    + diff --git a/getScreenId.js/README.md b/getScreenId.js/README.md index 70284f9f..012b38ad 100644 --- a/getScreenId.js/README.md +++ b/getScreenId.js/README.md @@ -36,7 +36,7 @@ - + ``` ```javascript diff --git a/getStats/README.md b/getStats/README.md index fad4f81c..5af8b798 100755 --- a/getStats/README.md +++ b/getStats/README.md @@ -1,4 +1,6 @@ -## [getStats.js](https://github.com/muaz-khan/getStats) [![npm](https://img.shields.io/npm/v/getstats.svg)](https://npmjs.org/package/getstats) [![downloads](https://img.shields.io/npm/dm/getstats.svg)](https://npmjs.org/package/getstats) +# [getStats.js](https://github.com/muaz-khan/getStats) / [Demo](https://www.webrtc-experiment.com/getStats/) + +[![npm](https://img.shields.io/npm/v/getstats.svg)](https://npmjs.org/package/getstats) [![downloads](https://img.shields.io/npm/dm/getstats.svg)](https://npmjs.org/package/getstats) A tiny JavaScript library using [WebRTC getStats API](http://dev.w3.org/2011/webrtc/editor/webrtc.html#dom-peerconnection-getstats) to return peer connection stats i.e. bandwidth usage, packets lost, local/remote ip addresses and ports, type of connection etc. @@ -6,6 +8,13 @@ It is MIT Licenced, whi ``` npm install getstats + +cd node_modules +cd getstats +node server.js + +# and open: +# http://localhost:9999/ ``` To use it: @@ -14,7 +23,7 @@ To use it: ``` -## Link the library +# Link the library ```html @@ -24,7 +33,11 @@ Or link specific build: * https://github.com/muaz-khan/getStats/releases -## window.getStats +```html + +``` + +# `window.getStats` To invoke directly: @@ -32,7 +45,7 @@ To invoke directly: getStats(peer, callback, interval); ``` -## RTCPeerConnection.prototype.getPeerStats +# RTCPeerConnection.prototype.getPeerStats Or, to setup an instance method: @@ -62,10 +75,10 @@ RTCPeerConnection.prototype.getStats = window.getStats; RTCPeerConnection.prototype.intanceMethodNamae = window.getStats; ``` -## Usage +# Usage ```javascript -var rtcPeerConnection = new RTCPeerConnection(iceServers); +var rtcPeerConnection = new RTCPeerConnection(rtcConfig); var repeatInterval = 2000; // 2000 ms == 2 seconds rtcPeerConnection.getPeerStats(function(result) { @@ -85,7 +98,7 @@ rtcPeerConnection.getPeerStats(function(result) { }, repeatInterval); ``` -## Firefox? +# Firefox? ```javascript peer.getStats(peer.getLocalStreams()[0].getAudioTracks()[0], function(results) { @@ -93,61 +106,85 @@ peer.getStats(peer.getLocalStreams()[0].getAudioTracks()[0], function(results) { }, 5 * 1000); ``` -## result.audio - -1. availableBandwidth -2. inputLevel -3. packetsLost -3. rtt -4. packetsSent -5. bytesSent - -## result.video - -1. availableBandwidth -2. googFrameHeightInput -3. googFrameWidthInput -4. googCaptureQueueDelayMsPerS -5. rtt -6. packetsLost -7. packetsSent -8. googEncodeUsagePercent -9. googCpuLimitedResolution -10. googNacksReceived -11. googFrameRateInput -12. googPlisReceived -13. googViewLimitedResolution -14. googCaptureJitterMs -15. googAvgEncodeMs -16. googFrameHeightSent -17. googFrameRateSent -18. googBandwidthLimitedResolution -19. googFrameWidthSent -20. googFirsReceived -21. bytesSent - -## result.connectionType - -1. local.candidateType -2. local.ipAddress -3. remote.candidateType -4. remote.ipAddress -5. transport - -## result.results +# `result.datachannel` -It is an array that is returned by browser's native PeerConnection API. +```javascript +// states => open or close +alert(result.datachannel.state === 'open'); +``` + +# `result.isOfferer` + +Offerer is the person who invoked `createOffer` method. + +# `result.encryption` -## Credits +To detect which tech is used to encrypt your connections. -[Muaz Khan](https://github.com/muaz-khan): +```javascript +alert(result.encryption === 'sha-256'); +``` + +# `result.nomore()` + +This function can be used to ask to stop invoking getStats API. + +```javascript +btnStopGetStats.onclick = function() { + getStatsResult.nomore(); +}; +``` + +# `result.audio` + +1. `result.audio.availableBandwidth` +2. `result.audio.inputLevel` +3. `result.audio.packetsLost` +3. `result.audio.rtt` +4. `result.audio.packetsSent` +5. `result.audio.bytesSent` + +# `result.video` + +1. `result.video.availableBandwidth` +2. `result.video.googFrameHeightInput` +3. `result.video.googFrameWidthInput` +4. `result.video.googCaptureQueueDelayMsPerS` +5. `result.video.rtt` +6. `result.video.packetsLost` +7. `result.video.packetsSent` +8. `result.video.googEncodeUsagePercent` +9. `result.video.googCpuLimitedResolution` +10. `result.video.googNacksReceived` +11. `result.video.googFrameRateInput` +12. `result.video.googPlisReceived` +13. `result.video.googViewLimitedResolution` +14. `result.video.googCaptureJitterMs` +15. `result.video.googAvgEncodeMs` +16. `result.video.googFrameHeightSent` +17. `result.video.googFrameRateSent` +18. `result.video.googBandwidthLimitedResolution` +19. `result.video.googFrameWidthSent` +20. `result.video.googFirsReceived` +21. `result.video.bytesSent` + +# `result.connectionType` + +1. `result.connectionType.local.candidateType` +2. `result.connectionType.local.ipAddress` +3. `result.connectionType.local.networkType` +4. `result.connectionType.remote.candidateType` +5. `result.connectionType.remote.ipAddress` +6. `result.connectionType.transport` + +# `result.results` + +It is an array that is returned by browser's native PeerConnection API. -1. Personal Webpage: http://www.muazkhan.com -2. Email: muazkh@gmail.com -3. Twitter: https://twitter.com/muazkh and https://twitter.com/WebRTCWeb -4. Google+: https://plus.google.com/+WebRTC-Experiment -5. Facebook: https://www.facebook.com/WebRTC +```javascript +console.log(result.results); +``` ## License -[getStats.js](https://github.com/muaz-khan/getStats) is released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](https://plus.google.com/+MuazKhan). +[getStats.js](https://github.com/muaz-khan/getStats) is released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](http://www.MuazKhan.com/). diff --git a/getStats/getStats.js b/getStats/getStats.js index 54ff5ffd..9d614c7c 100755 --- a/getStats/getStats.js +++ b/getStats/getStats.js @@ -1,4 +1,5 @@ -// Last time updated at Jan 07, 2016, 08:32:23 +// Last time updated at Feb 08, 2016, 08:32:23 + // Latest file can be found here: https://cdn.webrtc-experiment.com/getStats.js // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence @@ -15,7 +16,8 @@ getStats(rtcPeerConnection, function(result) { result.connectionType.transport }); */ -(function() { + +;(function() { var RTCPeerConnection; if (typeof webkitRTCPeerConnection !== 'undefined') { RTCPeerConnection = webkitRTCPeerConnection; @@ -69,57 +71,69 @@ getStats(rtcPeerConnection, function(result) { for (var i = 0; i < results.length; ++i) { var res = results[i]; + if(res.datachannelid && res.type === 'datachannel') { + result.datachannel = { + state: res.state // open or connecting + } + } + + if(res.type === 'googLibjingleSession') { + result.isOfferer = res.googInitiator; + } + + if(res.type == 'googCertificate') { + result.encryption = res.googFingerprintAlgorithm; + } + if (res.googCodecName == 'opus' && res.bytesSent) { - if (!globalObject.audio.prevBytesSent) - globalObject.audio.prevBytesSent = res.bytesSent; + var kilobytes = 0; + if(!!res.bytesSent) { + if (!globalObject.audio.prevBytesSent) { + globalObject.audio.prevBytesSent = res.bytesSent; + } - var bytes = res.bytesSent - globalObject.audio.prevBytesSent; - globalObject.audio.prevBytesSent = res.bytesSent; + var bytes = res.bytesSent - globalObject.audio.prevBytesSent; + globalObject.audio.prevBytesSent = res.bytesSent; - var kilobytes = bytes / 1024; + kilobytes = bytes / 1024; + } - result.audio = merge(result.audio, { - availableBandwidth: kilobytes.toFixed(1), - inputLevel: res.audioInputLevel, - packetsLost: res.packetsLost, - rtt: res.googRtt, - packetsSent: res.packetsSent, - bytesSent: res.bytesSent - }); + if(!result.audio) { + result.audio = res; + } + + result.audio.availableBandwidth = kilobytes.toFixed(1); } if (res.googCodecName == 'VP8') { - if (!globalObject.video.prevBytesSent) + // if(!globalObject.) + // bytesReceived + // packetsReceived + // timestamp + var kilobytes = 0; + if(!!res.bytesSent) { + if (!globalObject.video.prevBytesSent) { + globalObject.video.prevBytesSent = res.bytesSent; + } + + var bytes = res.bytesSent - globalObject.video.prevBytesSent; globalObject.video.prevBytesSent = res.bytesSent; - var bytes = res.bytesSent - globalObject.video.prevBytesSent; - globalObject.video.prevBytesSent = res.bytesSent; - - var kilobytes = bytes / 1024; - - result.video = merge(result.video, { - availableBandwidth: kilobytes.toFixed(1), - googFrameHeightInput: res.googFrameHeightInput, - googFrameWidthInput: res.googFrameWidthInput, - googCaptureQueueDelayMsPerS: res.googCaptureQueueDelayMsPerS, - rtt: res.googRtt, - packetsLost: res.packetsLost, - packetsSent: res.packetsSent, - googEncodeUsagePercent: res.googEncodeUsagePercent, - googCpuLimitedResolution: res.googCpuLimitedResolution, - googNacksReceived: res.googNacksReceived, - googFrameRateInput: res.googFrameRateInput, - googPlisReceived: res.googPlisReceived, - googViewLimitedResolution: res.googViewLimitedResolution, - googCaptureJitterMs: res.googCaptureJitterMs, - googAvgEncodeMs: res.googAvgEncodeMs, - googFrameHeightSent: res.googFrameHeightSent, - googFrameRateSent: res.googFrameRateSent, - googBandwidthLimitedResolution: res.googBandwidthLimitedResolution, - googFrameWidthSent: res.googFrameWidthSent, - googFirsReceived: res.googFirsReceived, - bytesSent: res.bytesSent - }); + kilobytes = bytes / 1024; + } + + if(!result.video) { + result.video = res; + } + + result.video.availableBandwidth = kilobytes.toFixed(1); + + if(res.googFrameHeightReceived && res.googFrameWidthReceived) { + result.resolutions = { + width: res.googFrameWidthReceived, + height: res.googFrameHeightReceived + }; + } } if (res.type == 'VideoBwe') { @@ -149,6 +163,50 @@ getStats(rtcPeerConnection, function(result) { transport: res.googTransportType }; } + + var systemNetworkType = ((navigator.connection || {}).type || 'unknown').toString().toLowerCase(); + + if(res.type === 'localcandidate') { + if(!result.connectionType) { + result.connectionType = {}; + } + + result.connectionType.local = { + candidateType: res.candidateType, + ipAddress: res.ipAddress + ':' + res.portNumber, + networkType: res.networkType/* || systemNetworkType */ || 'unknown', + transport: res.transport + } + } + + if(res.type === 'remotecandidate') { + if(!result.connectionType) { + result.connectionType = {}; + } + + result.connectionType.local = { + candidateType: res.candidateType, + ipAddress: res.ipAddress + ':' + res.portNumber, + networkType: res.networkType || systemNetworkType, + transport: res.transport + } + } + } + + try { + if(peer.iceConnectionState.search(/failed|closed/gi) !== -1) { + nomore = true; + } + } + catch(e) { + nomore = true; + } + + if(nomore === true) { + if(result.datachannel) { + result.datachannel.state = 'close'; + } + result.ended = true; } callback(result); diff --git a/getStats/index.html b/getStats/index.html new file mode 100644 index 00000000..3b2cd045 --- /dev/null +++ b/getStats/index.html @@ -0,0 +1,353 @@ + + + + + + + getStats.js Demo ® Muaz Khan + + + + + + + + + + + + + + + + + + +
    +
    +

    + getStats.js Demo ® + Muaz Khan +

    +

    + HOME + © + Muaz Khan . + @WebRTCWeb . + Github . + Latest issues . + What's New? +

    +
    + +
    + +
    +
    + +

    Offerer-to-Answerer

    + +
    +
    + +

    Answerer-to-Offerer

    + +
    +
    + + + + + + + +
    +

    getStats.js Logs


    + + + + + + + + + + + +
    PeerIdIsOffererConnectionInfoDataChannelEncryptedAsAudioAvailBandwidthVideoAvailBandwidth
    +
    + + + +
    +

    getStats Issues +

    +
    +
    + +
    + +
    +

    Latest Updates +

    +
    +
    +
    + + + + + + + + + + + diff --git a/getStats/package.json b/getStats/package.json index f6aa31ac..2f6d151a 100755 --- a/getStats/package.json +++ b/getStats/package.json @@ -1,7 +1,7 @@ { "name": "getstats", "preferGlobal": true, - "version": "1.0.3", + "version": "1.0.4", "author": { "name": "Muaz Khan", "email": "muazkh@gmail.com", @@ -9,9 +9,9 @@ }, "description": "A tiny JavaScript library using WebRTC getStats API to return peer connection stats i.e. bandwidth usage, packets lost, local/remote ip addresses and ports, type of connection etc.", "scripts": { - "start": "node getStats.js" + "start": "node server.js" }, - "main": "./getStats.js", + "main": "./server.js", "repository": { "type": "git", "url": "https://github.com/muaz-khan/getStats.git" diff --git a/getStats/server.js b/getStats/server.js new file mode 100644 index 00000000..b4e379ca --- /dev/null +++ b/getStats/server.js @@ -0,0 +1,74 @@ +// http://127.0.0.1:9999 +// http://localhost:9999 + +var server = require('http'), + url = require('url'), + path = require('path'), + fs = require('fs'); + +function serverHandler(request, response) { + var uri = url.parse(request.url).pathname, + filename = path.join(process.cwd(), uri); + + fs.exists(filename, function(exists) { + if (!exists) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + filename + '\n'); + response.end(); + return; + } + + if (filename.indexOf('favicon.ico') !== -1) { + return; + } + + var isWin = !!process.platform.match(/^win/); + + if (fs.statSync(filename).isDirectory() && !isWin) { + filename += '/index.html'; + } else if (fs.statSync(filename).isDirectory() && !!isWin) { + filename += '\\index.html'; + } + + fs.readFile(filename, 'binary', function(err, file) { + if (err) { + response.writeHead(500, { + 'Content-Type': 'text/plain' + }); + response.write(err + '\n'); + response.end(); + return; + } + + var contentType; + + if (filename.indexOf('.html') !== -1) { + contentType = 'text/html'; + } + + if (filename.indexOf('.js') !== -1) { + contentType = 'application/javascript'; + } + + if (contentType) { + response.writeHead(200, { + 'Content-Type': contentType + }); + } else response.writeHead(200); + + response.write(file, 'binary'); + response.end(); + }); + }); +} + +var app; + +app = server.createServer(serverHandler); + +app = app.listen(process.env.PORT || 9999, process.env.IP || "0.0.0.0", function() { + var addr = app.address(); + console.log("Server listening at", addr.address + ":" + addr.port); +}); diff --git a/meeting/index.html b/meeting/index.html index de9d6a8d..e531475f 100644 --- a/meeting/index.html +++ b/meeting/index.html @@ -123,7 +123,7 @@

    Remote Peers

    + @@ -290,33 +291,6 @@

    Shared DIVs will be visible here ↓

    }; }; - // overriding "openSignalingChannel" to use WebSockets as signaling gateway! - channel.openSignalingChannel = function(config) { - config.channel = config.channel || this.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); - var websocket = new WebSocket('wss://wsnodejs.nodejitsu.com:443/'); - websocket.channel = config.channel; - websocket.onopen = function() { - websocket.push(JSON.stringify({ - open: true, - channel: config.channel - })); - if (config.callback) config.callback(websocket); - }; - websocket.onerror = function() { - alert('WebSocket connection has issues. Signaling is failed.'); - }; - websocket.onmessage = function(event) { - config.onmessage(JSON.parse(event.data)); - }; - websocket.push = websocket.send; - websocket.send = function(data) { - websocket.push(JSON.stringify({ - data: data, - channel: config.channel - })); - }; - }; - // a text message or data channel.onmessage = function(data, userid, latency) { onMessage(data); diff --git a/screen-sharing/screen.js b/screen-sharing/screen.js index b0fba7d7..264e7a1c 100644 --- a/screen-sharing/screen.js +++ b/screen-sharing/screen.js @@ -56,7 +56,7 @@ alert(Firefox_Screen_Capturing_Warning); } - console.error(e); + console.error(error); }); }); } diff --git a/socket.io/index.html b/socket.io/index.html index ef335436..7cf76014 100644 --- a/socket.io/index.html +++ b/socket.io/index.html @@ -13,7 +13,7 @@ - + @@ -120,7 +120,7 @@

    var channel = location.href.replace(/\/|:|#|%|\.|\[|\]/g, ''); var sender = Math.round(Math.random() * 999999999) + 999999999; - var SIGNALING_SERVER = 'https://signaling-muazkh.c9.io:443/'; + var SIGNALING_SERVER = 'https://socketio-signaling.herokuapp.com/'; io.connect(SIGNALING_SERVER).emit('new-channel', { channel: channel, sender: sender