From a276f4e93eb431f242bdc1bc9b705651fe4ec271 Mon Sep 17 00:00:00 2001
From: "Jonathan Almeida (:jonalmeida)"
Date: Tue, 10 Nov 2015 00:03:55 -0500
Subject: [PATCH 01/71] Bug 1209293 - Flip default options for click-to-view
images r=mfinkle
---
mobile/android/app/mobile.js | 6 +++---
mobile/android/components/ImageBlockingPolicy.js | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/mobile/android/app/mobile.js b/mobile/android/app/mobile.js
index f012635e6d7..67f53df3f39 100644
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -350,9 +350,9 @@ pref("browser.link.open_newwindow", 3);
// 0=force all new windows to tabs, 1=don't force, 2=only force those with no features set
pref("browser.link.open_newwindow.restriction", 0);
-// image blocking policy
-// 0=disabled, 1=enabled, 2=cellular-only
-pref("browser.image_blocking", 0);
+// show images option
+// 0=never, 1=always, 2=cellular-only
+pref("browser.image_blocking", 1);
// controls which bits of private data to clear. by default we clear them all.
pref("privacy.item.cache", true);
diff --git a/mobile/android/components/ImageBlockingPolicy.js b/mobile/android/components/ImageBlockingPolicy.js
index 22441fe84c4..e4156392066 100644
--- a/mobile/android/components/ImageBlockingPolicy.js
+++ b/mobile/android/components/ImageBlockingPolicy.js
@@ -45,7 +45,7 @@ ImageBlockingPolicy.prototype = {
// nsIContentPolicy interface implementation
shouldLoad: function(contentType, contentLocation, requestOrigin, node, mimeTypeGuess, extra) {
// When enabled or when on cellular, and option for cellular-only is selected
- if (this._enabled() == OPTION_ALWAYS || (this._enabled() == OPTION_WIFI_ONLY && this._usingCellular())) {
+ if (this._enabled() == OPTION_NEVER || (this._enabled() == OPTION_WIFI_ONLY && this._usingCellular())) {
if (contentType === Ci.nsIContentPolicy.TYPE_IMAGE || contentType === Ci.nsIContentPolicy.TYPE_IMAGESET) {
// Accept any non-http(s) image URLs
if (!contentLocation.schemeIs("http") && !contentLocation.schemeIs("https")) {
From 4e72fb07acfe7ffe7b656ba10eab5ee34e80f92b Mon Sep 17 00:00:00 2001
From: Mark Banner
Date: Tue, 10 Nov 2015 08:58:10 +0000
Subject: [PATCH 02/71] Bug 1210865 - Update OpenTok library to version 2.6.8.
r=dmose
---
.../loop/content/shared/libs/sdk.js | 16126 ++++++++--------
1 file changed, 8537 insertions(+), 7589 deletions(-)
diff --git a/browser/components/loop/content/shared/libs/sdk.js b/browser/components/loop/content/shared/libs/sdk.js
index 5cc2c378639..d55d50882f4 100755
--- a/browser/components/loop/content/shared/libs/sdk.js
+++ b/browser/components/loop/content/shared/libs/sdk.js
@@ -1,26 +1,23 @@
/**
- * @license OpenTok JavaScript Library v2.5.2 f4508e1 2015Q1.patch.1
- * http://www.tokbox.com/
+ * @license OpenTok.js v2.6.8 fae7901 HEAD
*
- * Copyright (c) 2014 TokBox, Inc.
- * Released under the MIT license
- * http://opensource.org/licenses/MIT
+ * Copyright (c) 2010-2015 TokBox, Inc.
+ * Subject to the applicable Software Development Kit (SDK) License Agreement:
+ * https://tokbox.com/support/sdk_license
*
- * Date: July 13 05:38:08 2015
+ * Date: October 28 03:45:23 2015
*/
-
-
!(function(window) {
!(function(window, OTHelpers, undefined) {
/**
- * @license Common JS Helpers on OpenTok 0.3.0 f151b47 HEAD
+ * @license Common JS Helpers on OpenTok 0.4.1 259ca46 v0.4.1-branch
* http://www.tokbox.com/
*
* Copyright (c) 2015 TokBox, Inc.
*
- * Date: July 13 05:37:51 2015
+ * Date: October 28 03:45:12 2015
*
*/
@@ -556,6 +553,22 @@ OTHelpers.invert = function(obj) {
return result;
};
+
+// A helper for the common case of making a simple promise that is either
+// resolved or rejected straight away.
+//
+// If the +err+ param is provide then the promise will be rejected, otherwise
+// it will resolve.
+//
+OTHelpers.makeSimplePromise = function(err) {
+ return new OTHelpers.RSVP.Promise(function(resolve, reject) {
+ if (err === void 0) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
+};
// tb_require('../../../helpers.js')
/* exported EventableEvent */
@@ -634,6 +647,1919 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged,
return setState;
};
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+
+
+var getErrorLocation;
+
+// Properties that we'll acknowledge from the JS Error object
+var safeErrorProps = [
+ 'description',
+ 'fileName',
+ 'lineNumber',
+ 'message',
+ 'name',
+ 'number',
+ 'stack'
+];
+
+
+// OTHelpers.Error
+//
+// A construct to contain error information that also helps with extracting error
+// context, such as stack trace.
+//
+// @constructor
+// @memberof OTHelpers
+// @method Error
+//
+// @param {String} message
+// Optional. The error message
+//
+// @param {Object} props
+// Optional. A dictionary of properties containing extra Error info.
+//
+//
+// @example Create a simple error with juts a custom message
+// var error = new OTHelpers.Error('Something Broke!');
+// error.message === 'Something Broke!';
+//
+// @example Create an Error with a message and a name
+// var error = new OTHelpers.Error('Something Broke!', 'FooError');
+// error.message === 'Something Broke!';
+// error.name === 'FooError';
+//
+// @example Create an Error with a message, name, and custom properties
+// var error = new OTHelpers.Error('Something Broke!', 'FooError', {
+// foo: 'bar',
+// listOfImportantThings: [1,2,3,4]
+// });
+// error.message === 'Something Broke!';
+// error.name === 'FooError';
+// error.foo === 'bar';
+// error.listOfImportantThings == [1,2,3,4];
+//
+// @example Create an Error from a Javascript Error
+// var error = new OTHelpers.Error(domSyntaxError);
+// error.message === domSyntaxError.message;
+// error.name === domSyntaxError.name === 'SyntaxError';
+// // ...continues for each properties of domSyntaxError
+//
+// @references
+// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
+// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
+// * http://www.w3.org/TR/dom/#interface-domerror
+//
+//
+// @todo
+// * update usage in OTMedia
+// * replace error handling in OT.js
+// * normalise stack behaviour under Chrome/Node/Safari with other browsers
+// * unit test for stack parsing
+//
+//
+OTHelpers.Error = function (message, name, props) {
+ switch (arguments.length) {
+ case 1:
+ if ($.isObject(message)) {
+ props = message;
+ name = void 0;
+ message = void 0;
+ }
+ // Otherwise it's the message
+ break;
+
+ case 2:
+ if ($.isObject(name)) {
+ props = name;
+ name = void 0;
+ }
+ // Otherwise name is actually the name
+
+ break;
+ }
+
+ if ( props instanceof Error) {
+ // Special handling of this due to Chrome weirdness. It seems that
+ // properties of the Error object, and it's children, are not
+ // enumerable in Chrome?
+ for (var i = 0, num = safeErrorProps.length; i < num; ++i) {
+ this[safeErrorProps[i]] = props[safeErrorProps[i]];
+ }
+ }
+ else if ( $.isObject(props)) {
+ // Use an custom properties that are provided
+ for (var key in props) {
+ if (props.hasOwnProperty(key)) {
+ this[key] = props[key];
+ }
+ }
+ }
+
+ // If any of the fundamental properties are missing then try and
+ // extract them.
+ if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) {
+ var err = getErrorLocation();
+
+ if (!this.fileName && err.fileName) {
+ this.fileName = err.fileName;
+ }
+
+ if (!this.lineNumber && err.lineNumber) {
+ this.lineNumber = err.lineNumber;
+ }
+
+ if (!this.columnNumber && err.columnNumber) {
+ this.columnNumber = err.columnNumber;
+ }
+
+ if (!this.stack && err.stack) {
+ this.stack = err.stack;
+ }
+ }
+
+ if (!this.message && message) this.message = message;
+ if (!this.name && name) this.name = name;
+};
+
+OTHelpers.Error.prototype.toString =
+OTHelpers.Error.prototype.valueOf = function() {
+ var locationDetails = '';
+ if (this.fileName) locationDetails += ' ' + this.fileName;
+ if (this.lineNumber) {
+ locationDetails += ' ' + this.lineNumber;
+ if (this.columnNumber) locationDetails += ':' + this.columnNumber;
+ }
+
+ return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>';
+};
+
+
+// Normalise err.stack so that it is the same format as the other browsers
+// We skip the first two frames so that we don't capture getErrorLocation() and
+// the callee.
+//
+// Used by Environments that support the StackTrace API. (Chrome, Node, Opera)
+//
+var prepareStackTrace = function prepareStackTrace (_, stack){
+ return $.map(stack.slice(2), function(frame) {
+ var _f = {
+ fileName: frame.getFileName(),
+ linenumber: frame.getLineNumber(),
+ columnNumber: frame.getColumnNumber()
+ };
+
+ if (frame.getFunctionName()) _f.functionName = frame.getFunctionName();
+ if (frame.getMethodName()) _f.methodName = frame.getMethodName();
+ if (frame.getThis()) _f.self = frame.getThis();
+
+ return _f;
+ });
+};
+
+
+// Black magic to retrieve error location info for various environments
+getErrorLocation = function getErrorLocation () {
+ var info = {},
+ callstack,
+ errLocation,
+ err;
+
+ switch ($.env.name) {
+ case 'Firefox':
+ case 'Safari':
+ case 'IE':
+
+ if ($.env.name !== 'IE') {
+ err = new Error();
+ }
+ else {
+ try {
+ window.call.js.is.explody;
+ }
+ catch(e) { err = e; }
+ }
+
+ callstack = (err.stack || '').split('\n');
+
+ //Remove call to getErrorLocation() and the callee
+ callstack.shift();
+ callstack.shift();
+
+ info.stack = callstack;
+
+ if ($.env.name === 'IE') {
+ // IE also includes the error message in it's stack trace
+ info.stack.shift();
+
+ // each line begins with some amounts of spaces and 'at', we remove
+ // these to normalise with the other browsers.
+ info.stack = $.map(callstack, function(call) {
+ return call.replace(/^\s+at\s+/g, '');
+ });
+ }
+
+ errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]);
+ if (errLocation) {
+ info.fileName = errLocation[1];
+ info.lineNumber = parseInt(errLocation[2], 10);
+ if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10);
+ }
+ break;
+
+ case 'Chrome':
+ case 'Node':
+ case 'Opera':
+ var currentPST = Error.prepareStackTrace;
+ Error.prepareStackTrace = prepareStackTrace;
+ err = new Error();
+ info.stack = err.stack;
+ Error.prepareStackTrace = currentPST;
+
+ var topFrame = info.stack[0];
+ info.lineNumber = topFrame.lineNumber;
+ info.columnNumber = topFrame.columnNumber;
+ info.fileName = topFrame.fileName;
+ if (topFrame.functionName) info.functionName = topFrame.functionName;
+ if (topFrame.methodName) info.methodName = topFrame.methodName;
+ if (topFrame.self) info.self = topFrame.self;
+ break;
+
+ default:
+ err = new Error();
+ if (err.stack) info.stack = err.stack.split('\n');
+ break;
+ }
+
+ if (err.message) info.message = err.message;
+ return info;
+};
+
+
+/*jshint browser:true, smarttabs:true*/
+/* global process */
+
+// tb_require('../helpers.js')
+
+
+// OTHelpers.env
+//
+// Contains information about the current environment.
+// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc)
+// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String
+// * **OTHelpers.env.userAgent** The raw user agent
+// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the
+// current version is greater than the argument
+//
+// Example
+// if (OTHelpers.env.versionGreaterThan('0.10.30')) {
+// // do something
+// }
+//
+(function() {
+ // @todo make exposing userAgent unnecessary
+ var version = -1;
+
+ // Returns true if otherVersion is greater than the current environment
+ // version.
+ var versionGEThan = function versionGEThan (otherVersion) {
+ if (otherVersion === version) return true;
+
+ if (typeof(otherVersion) === 'number' && typeof(version) === 'number') {
+ return otherVersion > version;
+ }
+
+ // The versions have multiple components (i.e. 0.10.30) and
+ // must be compared piecewise.
+ // Note: I'm ignoring the case where one version has multiple
+ // components and the other doesn't.
+ var v1 = otherVersion.split('.'),
+ v2 = version.split('.'),
+ versionLength = (v1.length > v2.length ? v2 : v1).length;
+
+ for (var i = 0; i < versionLength; ++i) {
+ if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
+ return true;
+ }
+ }
+
+ // Special case, v1 has extra components but the initial components
+ // were identical, we assume this means newer but it might also mean
+ // that someone changed versioning systems.
+ if (i < v1.length) {
+ return true;
+ }
+
+ return false;
+ };
+
+ var env = function() {
+ if (typeof(process) !== 'undefined' &&
+ typeof(process.versions) !== 'undefined' &&
+ typeof(process.versions.node) === 'string') {
+
+ version = process.versions.node;
+ if (version.substr(1) === 'v') version = version.substr(1);
+
+ // Special casing node to avoid gating window.navigator.
+ // Version will be a string rather than a float.
+ return {
+ name: 'Node',
+ version: version,
+ userAgent: 'Node ' + version,
+ iframeNeedsLoad: false,
+ versionGreaterThan: versionGEThan
+ };
+ }
+
+ var userAgent = window.navigator.userAgent.toLowerCase(),
+ appName = window.navigator.appName,
+ navigatorVendor,
+ name = 'unknown';
+
+ if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) {
+ name = 'Opera';
+
+ if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (userAgent.indexOf('firefox') > -1) {
+ name = 'Firefox';
+
+ if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (appName === 'Microsoft Internet Explorer') {
+ // IE 10 and below
+ name = 'IE';
+
+ if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) {
+ // IE 11+
+
+ name = 'IE';
+
+ if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if (userAgent.indexOf('chrome') > -1) {
+ name = 'Chrome';
+
+ if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+
+ } else if ((navigatorVendor = window.navigator.vendor) &&
+ navigatorVendor.toLowerCase().indexOf('apple') > -1) {
+ name = 'Safari';
+
+ if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
+ version = parseFloat( RegExp.$1 );
+ }
+ }
+
+ return {
+ name: name,
+ version: version,
+ userAgent: window.navigator.userAgent,
+ iframeNeedsLoad: userAgent.indexOf('webkit') < 0,
+ versionGreaterThan: versionGEThan
+ };
+ }();
+
+
+ OTHelpers.env = env;
+
+ OTHelpers.browser = function() {
+ return OTHelpers.env.name;
+ };
+
+ OTHelpers.browserVersion = function() {
+ return OTHelpers.env;
+ };
+
+})();
+// tb_require('../../environment.js')
+// tb_require('./event.js')
+
+var nodeEventing;
+
+if($.env.name === 'Node') {
+ (function() {
+ var EventEmitter = require('events').EventEmitter,
+ util = require('util');
+
+ // container for the EventEmitter behaviour. This prevents tight coupling
+ // caused by accidentally bleeding implementation details and API into whatever
+ // objects nodeEventing is applied to.
+ var NodeEventable = function NodeEventable () {
+ EventEmitter.call(this);
+
+ this.events = {};
+ };
+ util.inherits(NodeEventable, EventEmitter);
+
+
+ nodeEventing = function nodeEventing (/* self */) {
+ var api = new NodeEventable(),
+ _on = api.on,
+ _off = api.removeListener;
+
+
+ api.addListeners = function (eventNames, handler, context, closure) {
+ var listener = {handler: handler};
+ if (context) listener.context = context;
+ if (closure) listener.closure = closure;
+
+ $.forEach(eventNames, function(name) {
+ if (!api.events[name]) api.events[name] = [];
+ api.events[name].push(listener);
+
+ _on(name, handler);
+
+ var addedListener = name + ':added';
+ if (api.events[addedListener]) {
+ api.emit(addedListener, api.events[name].length);
+ }
+ });
+ };
+
+ api.removeAllListenersNamed = function (eventNames) {
+ var _eventNames = eventNames.split(' ');
+ api.removeAllListeners(_eventNames);
+
+ $.forEach(_eventNames, function(name) {
+ if (api.events[name]) delete api.events[name];
+ });
+ };
+
+ api.removeListeners = function (eventNames, handler, closure) {
+ function filterHandlers(listener) {
+ return !(listener.handler === handler && listener.closure === closure);
+ }
+
+ $.forEach(eventNames.split(' '), function(name) {
+ if (api.events[name]) {
+ _off(name, handler);
+ api.events[name] = $.filter(api.events[name], filterHandlers);
+ if (api.events[name].length === 0) delete api.events[name];
+
+ var removedListener = name + ':removed';
+ if (api.events[removedListener]) {
+ api.emit(removedListener, api.events[name] ? api.events[name].length : 0);
+ }
+ }
+ });
+ };
+
+ api.removeAllListeners = function () {
+ api.events = {};
+ api.removeAllListeners();
+ };
+
+ api.dispatchEvent = function(event, defaultAction) {
+ this.emit(event.type, event);
+
+ if (defaultAction) {
+ defaultAction.call(null, event);
+ }
+ };
+
+ api.trigger = $.bind(api.emit, api);
+
+
+ return api;
+ };
+ })();
+}
+
+// tb_require('../../environment.js')
+// tb_require('./event.js')
+
+var browserEventing;
+
+if($.env.name !== 'Node') {
+
+ browserEventing = function browserEventing (self, syncronous) {
+ var api = {
+ events: {}
+ };
+
+
+ // Call the defaultAction, passing args
+ function executeDefaultAction(defaultAction, args) {
+ if (!defaultAction) return;
+
+ defaultAction.apply(null, args.slice());
+ }
+
+ // Execute each handler in +listeners+ with +args+.
+ //
+ // Each handler will be executed async. On completion the defaultAction
+ // handler will be executed with the args.
+ //
+ // @param [Array] listeners
+ // An array of functions to execute. Each will be passed args.
+ //
+ // @param [Array] args
+ // An array of arguments to execute each function in +listeners+ with.
+ //
+ // @param [String] name
+ // The name of this event.
+ //
+ // @param [Function, Null, Undefined] defaultAction
+ // An optional function to execute after every other handler. This will execute even
+ // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
+ // handler would.
+ //
+ // @return Undefined
+ //
+ function executeListenersAsyncronously(name, args, defaultAction) {
+ var listeners = api.events[name];
+ if (!listeners || listeners.length === 0) return;
+
+ var listenerAcks = listeners.length;
+
+ $.forEach(listeners, function(listener) { // , index
+ function filterHandlers(_listener) {
+ return _listener.handler === listener.handler;
+ }
+
+ // We run this asynchronously so that it doesn't interfere with execution if an
+ // error happens
+ $.callAsync(function() {
+ try {
+ // have to check if the listener has not been removed
+ if (api.events[name] && $.some(api.events[name], filterHandlers)) {
+ (listener.closure || listener.handler).apply(listener.context || null, args);
+ }
+ }
+ finally {
+ listenerAcks--;
+
+ if (listenerAcks === 0) {
+ executeDefaultAction(defaultAction, args);
+ }
+ }
+ });
+ });
+ }
+
+
+ // This is identical to executeListenersAsyncronously except that handlers will
+ // be executed syncronously.
+ //
+ // On completion the defaultAction handler will be executed with the args.
+ //
+ // @param [Array] listeners
+ // An array of functions to execute. Each will be passed args.
+ //
+ // @param [Array] args
+ // An array of arguments to execute each function in +listeners+ with.
+ //
+ // @param [String] name
+ // The name of this event.
+ //
+ // @param [Function, Null, Undefined] defaultAction
+ // An optional function to execute after every other handler. This will execute even
+ // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
+ // handler would.
+ //
+ // @return Undefined
+ //
+ function executeListenersSyncronously(name, args) { // defaultAction is not used
+ var listeners = api.events[name];
+ if (!listeners || listeners.length === 0) return;
+
+ $.forEach(listeners, function(listener) { // index
+ (listener.closure || listener.handler).apply(listener.context || null, args);
+ });
+ }
+
+ var executeListeners = syncronous === true ?
+ executeListenersSyncronously : executeListenersAsyncronously;
+
+
+ api.addListeners = function (eventNames, handler, context, closure) {
+ var listener = {handler: handler};
+ if (context) listener.context = context;
+ if (closure) listener.closure = closure;
+
+ $.forEach(eventNames, function(name) {
+ if (!api.events[name]) api.events[name] = [];
+ api.events[name].push(listener);
+
+ var addedListener = name + ':added';
+ if (api.events[addedListener]) {
+ executeListeners(addedListener, [api.events[name].length]);
+ }
+ });
+ };
+
+ api.removeListeners = function(eventNames, handler, context) {
+ function filterListeners(listener) {
+ var isCorrectHandler = (
+ listener.handler.originalHandler === handler ||
+ listener.handler === handler
+ );
+
+ return !(isCorrectHandler && listener.context === context);
+ }
+
+ $.forEach(eventNames, function(name) {
+ if (api.events[name]) {
+ api.events[name] = $.filter(api.events[name], filterListeners);
+ if (api.events[name].length === 0) delete api.events[name];
+
+ var removedListener = name + ':removed';
+ if (api.events[ removedListener]) {
+ executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]);
+ }
+ }
+ });
+ };
+
+ api.removeAllListenersNamed = function (eventNames) {
+ $.forEach(eventNames, function(name) {
+ if (api.events[name]) {
+ delete api.events[name];
+ }
+ });
+ };
+
+ api.removeAllListeners = function () {
+ api.events = {};
+ };
+
+ api.dispatchEvent = function(event, defaultAction) {
+ if (!api.events[event.type] || api.events[event.type].length === 0) {
+ executeDefaultAction(defaultAction, [event]);
+ return;
+ }
+
+ executeListeners(event.type, [event], defaultAction);
+ };
+
+ api.trigger = function(eventName, args) {
+ if (!api.events[eventName] || api.events[eventName].length === 0) {
+ return;
+ }
+
+ executeListeners(eventName, args);
+ };
+
+
+ return api;
+ };
+}
+
+/*jshint browser:false, smarttabs:true*/
+/* global window, require */
+
+// tb_require('../../helpers.js')
+// tb_require('../environment.js')
+
+if (window.OTHelpers.env.name === 'Node') {
+ var request = require('request');
+
+ OTHelpers.request = function(url, options, callback) {
+ var completion = function(error, response, body) {
+ var event = {response: response, body: body};
+
+ // We need to detect things that Request considers a success,
+ // but we consider to be failures.
+ if (!error && response.statusCode >= 200 &&
+ (response.statusCode < 300 || response.statusCode === 304) ) {
+ callback(null, event);
+ } else {
+ callback(error, event);
+ }
+ };
+
+ if (options.method.toLowerCase() === 'get') {
+ request.get(url, completion);
+ }
+ else {
+ request.post(url, options.body, completion);
+ }
+ };
+
+ OTHelpers.getJSON = function(url, options, callback) {
+ var extendedHeaders = require('underscore').extend(
+ {
+ 'Accept': 'application/json'
+ },
+ options.headers || {}
+ );
+
+ request.get({
+ url: url,
+ headers: extendedHeaders,
+ json: true
+ }, function(err, response) {
+ callback(err, response && response.body);
+ });
+ };
+}
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../../helpers.js')
+// tb_require('../environment.js')
+
+function formatPostData(data) { //, contentType
+ // If it's a string, we assume it's properly encoded
+ if (typeof(data) === 'string') return data;
+
+ var queryString = [];
+
+ for (var key in data) {
+ queryString.push(
+ encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
+ );
+ }
+
+ return queryString.join('&').replace(/\+/g, '%20');
+}
+
+if (window.OTHelpers.env.name !== 'Node') {
+
+ OTHelpers.xdomainRequest = function(url, options, callback) {
+ /*global XDomainRequest*/
+ var xdr = new XDomainRequest(),
+ _options = options || {},
+ _method = _options.method.toLowerCase();
+
+ if(!_method) {
+ callback(new Error('No HTTP method specified in options'));
+ return;
+ }
+
+ _method = _method.toUpperCase();
+
+ if(!(_method === 'GET' || _method === 'POST')) {
+ callback(new Error('HTTP method can only be '));
+ return;
+ }
+
+ function done(err, event) {
+ xdr.onload = xdr.onerror = xdr.ontimeout = function() {};
+ xdr = void 0;
+ callback(err, event);
+ }
+
+
+ xdr.onload = function() {
+ done(null, {
+ target: {
+ responseText: xdr.responseText,
+ headers: {
+ 'content-type': xdr.contentType
+ }
+ }
+ });
+ };
+
+ xdr.onerror = function() {
+ done(new Error('XDomainRequest of ' + url + ' failed'));
+ };
+
+ xdr.ontimeout = function() {
+ done(new Error('XDomainRequest of ' + url + ' timed out'));
+ };
+
+ xdr.open(_method, url);
+ xdr.send(options.body && formatPostData(options.body));
+
+ };
+
+ OTHelpers.request = function(url, options, callback) {
+ var request = new XMLHttpRequest(),
+ _options = options || {},
+ _method = _options.method;
+
+ if(!_method) {
+ callback(new Error('No HTTP method specified in options'));
+ return;
+ }
+
+ if (options.overrideMimeType) {
+ if (request.overrideMimeType) {
+ request.overrideMimeType(options.overrideMimeType);
+ }
+ delete options.overrideMimeType;
+ }
+
+ // Setup callbacks to correctly respond to success and error callbacks. This includes
+ // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore
+ // by default.
+ if (callback) {
+ OTHelpers.on(request, 'load', function(event) {
+ var status = event.target.status;
+
+ // We need to detect things that XMLHttpRequest considers a success,
+ // but we consider to be failures.
+ if ( status >= 200 && (status < 300 || status === 304) ) {
+ callback(null, event);
+ } else {
+ callback(event);
+ }
+ });
+
+ OTHelpers.on(request, 'error', callback);
+ }
+
+ request.open(options.method, url, true);
+
+ if (!_options.headers) _options.headers = {};
+
+ for (var name in _options.headers) {
+ if (!Object.prototype.hasOwnProperty.call(_options.headers, name)) {
+ continue;
+ }
+ request.setRequestHeader(name, _options.headers[name]);
+ }
+
+ request.send(options.body && formatPostData(options.body));
+ };
+
+
+ OTHelpers.getJSON = function(url, options, callback) {
+ options = options || {};
+
+ var done = function(error, event) {
+ if(error) {
+ callback(error, event && event.target && event.target.responseText);
+ } else {
+ var response;
+
+ try {
+ response = JSON.parse(event.target.responseText);
+ } catch(e) {
+ // Badly formed JSON
+ callback(e, event && event.target && event.target.responseText);
+ return;
+ }
+
+ callback(null, response, event);
+ }
+ };
+
+ if(options.xdomainrequest) {
+ OTHelpers.xdomainRequest(url, { method: 'GET' }, done);
+ } else {
+ var extendedHeaders = OTHelpers.extend({
+ 'Accept': 'application/json'
+ }, options.headers || {});
+
+ OTHelpers.get(url, OTHelpers.extend(options || {}, {
+ headers: extendedHeaders
+ }), done);
+ }
+
+ };
+
+}
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./environment.js')
+
+
+// Log levels for OTLog.setLogLevel
+var LOG_LEVEL_DEBUG = 5,
+ LOG_LEVEL_LOG = 4,
+ LOG_LEVEL_INFO = 3,
+ LOG_LEVEL_WARN = 2,
+ LOG_LEVEL_ERROR = 1,
+ LOG_LEVEL_NONE = 0;
+
+
+// There is a single global log level for every component that uses
+// the logs.
+var _logLevel = LOG_LEVEL_NONE;
+
+var setLogLevel = function setLogLevel (level) {
+ _logLevel = typeof(level) === 'number' ? level : 0;
+ return _logLevel;
+};
+
+
+OTHelpers.useLogHelpers = function(on){
+
+ // Log levels for OTLog.setLogLevel
+ on.DEBUG = LOG_LEVEL_DEBUG;
+ on.LOG = LOG_LEVEL_LOG;
+ on.INFO = LOG_LEVEL_INFO;
+ on.WARN = LOG_LEVEL_WARN;
+ on.ERROR = LOG_LEVEL_ERROR;
+ on.NONE = LOG_LEVEL_NONE;
+
+ var _logs = [],
+ _canApplyConsole = true;
+
+ try {
+ Function.prototype.bind.call(window.console.log, window.console);
+ } catch (err) {
+ _canApplyConsole = false;
+ }
+
+ // Some objects can't be logged in the console, mostly these are certain
+ // types of native objects that are exposed to JS. This is only really a
+ // problem with IE, hence only the IE version does anything.
+ var makeLogArgumentsSafe = function(args) { return args; };
+
+ if (OTHelpers.env.name === 'IE') {
+ makeLogArgumentsSafe = function(args) {
+ return [toDebugString(prototypeSlice.apply(args))];
+ };
+ }
+
+ // Generates a logging method for a particular method and log level.
+ //
+ // Attempts to handle the following cases:
+ // * the desired log method doesn't exist, call fallback (if available) instead
+ // * the console functionality isn't available because the developer tools (in IE)
+ // aren't open, call fallback (if available)
+ // * attempt to deal with weird IE hosted logging methods as best we can.
+ //
+ function generateLoggingMethod(method, level, fallback) {
+ return function() {
+ if (on.shouldLog(level)) {
+ var cons = window.console,
+ args = makeLogArgumentsSafe(arguments);
+
+ // In IE, window.console may not exist if the developer tools aren't open
+ // This also means that cons and cons[method] can appear at any moment
+ // hence why we retest this every time.
+ if (cons && cons[method]) {
+ // the desired console method isn't a real object, which means
+ // that we can't use apply on it. We force it to be a real object
+ // using Function.bind, assuming that's available.
+ if (cons[method].apply || _canApplyConsole) {
+ if (!cons[method].apply) {
+ cons[method] = Function.prototype.bind.call(cons[method], cons);
+ }
+
+ cons[method].apply(cons, args);
+ }
+ else {
+ // This isn't the same result as the above, but it's better
+ // than nothing.
+ cons[method](args);
+ }
+ }
+ else if (fallback) {
+ fallback.apply(on, args);
+
+ // Skip appendToLogs, we delegate entirely to the fallback
+ return;
+ }
+
+ appendToLogs(method, makeLogArgumentsSafe(arguments));
+ }
+ };
+ }
+
+ on.log = generateLoggingMethod('log', on.LOG);
+
+ // Generate debug, info, warn, and error logging methods, these all fallback to on.log
+ on.debug = generateLoggingMethod('debug', on.DEBUG, on.log);
+ on.info = generateLoggingMethod('info', on.INFO, on.log);
+ on.warn = generateLoggingMethod('warn', on.WARN, on.log);
+ on.error = generateLoggingMethod('error', on.ERROR, on.log);
+
+
+ on.setLogLevel = function(level) {
+ on.debug('TB.setLogLevel(' + _logLevel + ')');
+ return setLogLevel(level);
+ };
+
+ on.getLogs = function() {
+ return _logs;
+ };
+
+ // Determine if the level is visible given the current logLevel.
+ on.shouldLog = function(level) {
+ return _logLevel >= level;
+ };
+
+ // Format the current time nicely for logging. Returns the current
+ // local time.
+ function formatDateStamp() {
+ var now = new Date();
+ return now.toLocaleTimeString() + now.getMilliseconds();
+ }
+
+ function toJson(object) {
+ try {
+ return JSON.stringify(object);
+ } catch(e) {
+ return object.toString();
+ }
+ }
+
+ function toDebugString(object) {
+ var components = [];
+
+ if (typeof(object) === 'undefined') {
+ // noop
+ }
+ else if (object === null) {
+ components.push('NULL');
+ }
+ else if (OTHelpers.isArray(object)) {
+ for (var i=0; i 0) {
+ queueRunning = true;
+ var curr = logQueue[0];
+
+ // Remove the current item and send the next log
+ var processNextItem = function() {
+ logQueue.shift();
+ queueRunning = false;
+ throttledPost();
+ };
+
+ if (curr) {
+ send(curr.data, curr.isQos, function(err) {
+ if (err) {
+ var debugMsg = 'Failed to send ClientEvent, moving on to the next item.';
+ if (debugFn) {
+ debugFn(debugMsg);
+ } else {
+ console.log(debugMsg);
+ }
+ if (curr.onComplete) {
+ curr.onComplete(err);
+ }
+ // There was an error, move onto the next item
+ }
+ if (curr.onComplete) {
+ curr.onComplete(err);
+ }
+ setTimeout(processNextItem, 50);
+ });
+ }
+ }
+ },
+
+ post = function(data, onComplete, isQos) {
+ logQueue.push({
+ data: data,
+ onComplete: onComplete,
+ isQos: isQos
+ });
+
+ throttledPost();
+ },
+
+ shouldThrottleError = function(code, type, partnerId) {
+ if (!partnerId) return false;
+
+ var errKey = [partnerId, type, code].join('_'),
+ //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId);
+ msgLimit = 100;
+ if (msgLimit === null || msgLimit === undefined) return false;
+ return (reportedErrors[errKey] || 0) <= msgLimit;
+ };
+
+ // Log an error via ClientEvents.
+ //
+ // @param [String] code
+ // @param [String] type
+ // @param [String] message
+ // @param [Hash] details additional error details
+ //
+ // @param [Hash] options the options to log the client event with.
+ // @option options [String] action The name of the Event that we are logging. E.g.
+ // 'TokShowLoaded'. Required.
+ // @option options [String] variation Usually used for Split A/B testing, when you
+ // have multiple variations of the +_action+.
+ // @option options [String] payload The payload. Required.
+ // @option options [String] sessionId The active OpenTok session, if there is one
+ // @option options [String] connectionId The active OpenTok connectionId, if there is one
+ // @option options [String] partnerId
+ // @option options [String] guid ...
+ // @option options [String] streamId ...
+ // @option options [String] section ...
+ // @option options [String] clientVersion ...
+ //
+ // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner
+ // from the dynamic config for X) of each error type for each partner. Reports can be
+ // disabled/enabled globally or on a per partner basis (per partner settings
+ // take precedence) using exceptionLogging.enabled.
+ //
+ this.logError = function(code, type, message, details, options) {
+ if (!options) options = {};
+ var partnerId = options.partnerId;
+
+ if (shouldThrottleError(code, type, partnerId)) {
+ //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' +
+ // code + ' for partner ' + (partnerId || 'No Partner Id'));
+ return;
+ }
+
+ var errKey = [partnerId, type, code].join('_'),
+ payload = details ? details : null;
+
+ reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ?
+ reportedErrors[errKey] + 1 : 1;
+ this.logEvent(OTHelpers.extend(options, {
+ action: type + '.' + code,
+ payload: payload
+ }), false);
+ };
+
+ // Log a client event to the analytics backend.
+ //
+ // @example Logs a client event called 'foo'
+ // this.logEvent({
+ // action: 'foo',
+ // payload: 'bar',
+ // sessionId: sessionId,
+ // connectionId: connectionId
+ // }, false)
+ //
+ // @param [Hash] data the data to log the client event with.
+ // @param [Boolean] qos Whether this is a QoS event.
+ // @param [Boolean] throttle A number specifying the ratio of events to be sent
+ // out of the total number of events (other events are not ignored). If not
+ // set to a number, all events are sent.
+ // @param [Number] completionHandler A completion handler function to call when the
+ // client event POST request succeeds or fails. If it fails, an error
+ // object is passed into the function. (See throttledPost().)
+ //
+ this.logEvent = function(data, qos, throttle, completionHandler) {
+ if (!qos) qos = false;
+
+ if (throttle && !isNaN(throttle)) {
+ if (Math.random() > throttle) {
+ return;
+ }
+ }
+
+ // remove properties that have null values:
+ for (var key in data) {
+ if (data.hasOwnProperty(key) && data[key] === null) {
+ delete data[key];
+ }
+ }
+
+ // TODO: catch error when stringifying an object that has a circular reference
+ data = JSON.stringify(data);
+
+ post(data, completionHandler, qos);
+ };
+
+ // Log a client QOS to the analytics backend.
+ // Log a client QOS to the analytics backend.
+ // @option options [String] action The name of the Event that we are logging.
+ // E.g. 'TokShowLoaded'. Required.
+ // @option options [String] variation Usually used for Split A/B testing, when
+ // you have multiple variations of the +_action+.
+ // @option options [String] payload The payload. Required.
+ // @option options [String] sessionId The active OpenTok session, if there is one
+ // @option options [String] connectionId The active OpenTok connectionId, if there is one
+ // @option options [String] partnerId
+ // @option options [String] guid ...
+ // @option options [String] streamId ...
+ // @option options [String] section ...
+ // @option options [String] clientVersion ...
+ //
+ this.logQOS = function(options) {
+ this.logEvent(options, true);
+ };
+ };
+
+})();
+
+// AJAX helpers
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./ajax/node.js')
+// tb_require('./ajax/browser.js')
+
+OTHelpers.get = function(url, options, callback) {
+ var _options = OTHelpers.extend(options || {}, {
+ method: 'GET'
+ });
+ OTHelpers.request(url, _options, callback);
+};
+
+
+OTHelpers.post = function(url, options, callback) {
+ var _options = OTHelpers.extend(options || {}, {
+ method: 'POST'
+ });
+
+ if(_options.xdomainrequest) {
+ OTHelpers.xdomainRequest(url, _options, callback);
+ } else {
+ OTHelpers.request(url, _options, callback);
+ }
+};
+
/*!
* This is a modified version of Robert Kieffer awesome uuid.js library.
* The only modifications we've made are to remove the Node.js specific
@@ -775,6 +2701,1360 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged,
OTHelpers.uuid = uuid;
}());
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('../vendor/uuid.js')
+// tb_require('./dom_events.js')
+
+(function() {
+
+ var _callAsync;
+
+ // Is true if window.postMessage is supported.
+ // This is not quite as simple as just looking for
+ // window.postMessage as some older versions of IE
+ // have a broken implementation of it.
+ //
+ var supportsPostMessage = (function () {
+ if (window.postMessage) {
+ // Check to see if postMessage fires synchronously,
+ // if it does, then the implementation of postMessage
+ // is broken.
+ var postMessageIsAsynchronous = true;
+ var oldOnMessage = window.onmessage;
+ window.onmessage = function() {
+ postMessageIsAsynchronous = false;
+ };
+ window.postMessage('', '*');
+ window.onmessage = oldOnMessage;
+ return postMessageIsAsynchronous;
+ }
+ })();
+
+ if (supportsPostMessage) {
+ var timeouts = [],
+ messageName = 'OTHelpers.' + OTHelpers.uuid.v4() + '.zero-timeout';
+
+ var removeMessageHandler = function() {
+ timeouts = [];
+
+ if(window.removeEventListener) {
+ window.removeEventListener('message', handleMessage);
+ } else if(window.detachEvent) {
+ window.detachEvent('onmessage', handleMessage);
+ }
+ };
+
+ var handleMessage = function(event) {
+ if (event.source === window &&
+ event.data === messageName) {
+
+ if(OTHelpers.isFunction(event.stopPropagation)) {
+ event.stopPropagation();
+ }
+ event.cancelBubble = true;
+
+ if (!window.___othelpers) {
+ removeMessageHandler();
+ return;
+ }
+
+ if (timeouts.length > 0) {
+ var args = timeouts.shift(),
+ fn = args.shift();
+
+ fn.apply(null, args);
+ }
+ }
+ };
+
+ // Ensure that we don't receive messages after unload
+ // Yes, this seems to really happen in IE sometimes, usually
+ // when iFrames are involved.
+ OTHelpers.on(window, 'unload', removeMessageHandler);
+
+ if(window.addEventListener) {
+ window.addEventListener('message', handleMessage, true);
+ } else if(window.attachEvent) {
+ window.attachEvent('onmessage', handleMessage);
+ }
+
+ _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
+ timeouts.push(prototypeSlice.call(arguments));
+ window.postMessage(messageName, '*');
+ };
+ }
+ else {
+ _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
+ var args = prototypeSlice.call(arguments),
+ fn = args.shift();
+
+ setTimeout(function() {
+ fn.apply(null, args);
+ }, 0);
+ };
+ }
+
+
+ // Calls the function +fn+ asynchronously with the current execution.
+ // This is most commonly used to execute something straight after
+ // the current function.
+ //
+ // Any arguments in addition to +fn+ will be passed to +fn+ when it's
+ // called.
+ //
+ // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync
+ // is preferable as it executes in a much more predictable time window,
+ // unlike setTimeout which could execute anywhere from 2ms to several thousand
+ // depending on the browser/context.
+ //
+ // It does this using window.postMessage, although if postMessage won't
+ // work it will fallback to setTimeout.
+ //
+ OTHelpers.callAsync = _callAsync;
+
+
+ // Wraps +handler+ in a function that will execute it asynchronously
+ // so that it doesn't interfere with it's exceution context if it raises
+ // an exception.
+ OTHelpers.createAsyncHandler = function(handler) {
+ return function() {
+ var args = prototypeSlice.call(arguments);
+
+ OTHelpers.callAsync(function() {
+ handler.apply(null, args);
+ });
+ };
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+// tb_require('./dom_events.js')
+
+OTHelpers.createElement = function(nodeName, attributes, children, doc) {
+ var element = (doc || document).createElement(nodeName);
+
+ if (attributes) {
+ for (var name in attributes) {
+ if (typeof(attributes[name]) === 'object') {
+ if (!element[name]) element[name] = {};
+
+ var subAttrs = attributes[name];
+ for (var n in subAttrs) {
+ element[name][n] = subAttrs[n];
+ }
+ }
+ else if (name === 'className') {
+ element.className = attributes[name];
+ }
+ else {
+ element.setAttribute(name, attributes[name]);
+ }
+ }
+ }
+
+ var setChildren = function(child) {
+ if(typeof child === 'string') {
+ element.innerHTML = element.innerHTML + child;
+ } else {
+ element.appendChild(child);
+ }
+ };
+
+ if($.isArray(children)) {
+ $.forEach(children, setChildren);
+ } else if(children) {
+ setChildren(children);
+ }
+
+ return element;
+};
+
+OTHelpers.createButton = function(innerHTML, attributes, events) {
+ var button = $.createElement('button', attributes, innerHTML);
+
+ if (events) {
+ for (var name in events) {
+ if (events.hasOwnProperty(name)) {
+ $.on(button, name, events[name]);
+ }
+ }
+
+ button._boundEvents = events;
+ }
+
+ return button;
+};
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+
+// DOM helpers
+
+var firstElementChild;
+
+// This mess is for IE8
+if( typeof(document) !== 'undefined' &&
+ document.createElement('div').firstElementChild !== void 0 ){
+ firstElementChild = function firstElementChild (parentElement) {
+ return parentElement.firstElementChild;
+ };
+}
+else {
+ firstElementChild = function firstElementChild (parentElement) {
+ var el = parentElement.firstChild;
+
+ do {
+ if(el.nodeType===1){
+ return el;
+ }
+ el = el.nextSibling;
+ } while(el);
+
+ return null;
+ };
+}
+
+
+ElementCollection.prototype.appendTo = function(parentElement) {
+ if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.');
+
+ return this.forEach(function(child) {
+ parentElement.appendChild(child);
+ });
+};
+
+ElementCollection.prototype.append = function() {
+ var parentElement = this.first;
+ if (!parentElement) return this;
+
+ $.forEach(prototypeSlice.call(arguments), function(child) {
+ parentElement.appendChild(child);
+ });
+
+ return this;
+};
+
+ElementCollection.prototype.prepend = function() {
+ if (arguments.length === 0) return this;
+
+ var parentElement = this.first,
+ elementsToPrepend;
+
+ if (!parentElement) return this;
+
+ elementsToPrepend = prototypeSlice.call(arguments);
+
+ if (!firstElementChild(parentElement)) {
+ parentElement.appendChild(elementsToPrepend.shift());
+ }
+
+ $.forEach(elementsToPrepend, function(element) {
+ parentElement.insertBefore(element, firstElementChild(parentElement));
+ });
+
+ return this;
+};
+
+ElementCollection.prototype.after = function(prevElement) {
+ if (!prevElement) throw new Error('after requires a DOMElement to insert after');
+
+ return this.forEach(function(element) {
+ if (element.parentElement) {
+ if (prevElement !== element.parentNode.lastChild) {
+ element.parentElement.insertBefore(element, prevElement);
+ }
+ else {
+ element.parentElement.appendChild(element);
+ }
+ }
+ });
+};
+
+ElementCollection.prototype.before = function(nextElement) {
+ if (!nextElement) {
+ throw new Error('before requires a DOMElement to insert before');
+ }
+
+ return this.forEach(function(element) {
+ if (element.parentElement) {
+ element.parentElement.insertBefore(element, nextElement);
+ }
+ });
+};
+
+ElementCollection.prototype.remove = function () {
+ return this.forEach(function(element) {
+ if (element.parentNode) {
+ element.parentNode.removeChild(element);
+ }
+ });
+};
+
+ElementCollection.prototype.empty = function () {
+ return this.forEach(function(element) {
+ // elements is a "live" NodesList collection. Meaning that the collection
+ // itself will be mutated as we remove elements from the DOM. This means
+ // that "while there are still elements" is safer than "iterate over each
+ // element" as the collection length and the elements indices will be modified
+ // with each iteration.
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+ });
+};
+
+
+// Detects when an element is not part of the document flow because
+// it or one of it's ancesters has display:none.
+ElementCollection.prototype.isDisplayNone = function() {
+ return this.some(function(element) {
+ if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
+ $(element).css('display') === 'none') return true;
+
+ if (element.parentNode && element.parentNode.style) {
+ return $(element.parentNode).isDisplayNone();
+ }
+ });
+};
+
+ElementCollection.prototype.findElementWithDisplayNone = function(element) {
+ return $.findElementWithDisplayNone(element);
+};
+
+
+
+OTHelpers.isElementNode = function(node) {
+ return node && typeof node === 'object' && node.nodeType === 1;
+};
+
+
+// @remove
+OTHelpers.removeElement = function(element) {
+ $(element).remove();
+};
+
+// @remove
+OTHelpers.removeElementById = function(elementId) {
+ return $('#'+elementId).remove();
+};
+
+// @remove
+OTHelpers.removeElementsByType = function(parentElem, type) {
+ return $(type, parentElem).remove();
+};
+
+// @remove
+OTHelpers.emptyElement = function(element) {
+ return $(element).empty();
+};
+
+
+
+
+
+// @remove
+OTHelpers.isDisplayNone = function(element) {
+ return $(element).isDisplayNone();
+};
+
+OTHelpers.findElementWithDisplayNone = function(element) {
+ if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
+ $.css(element, 'display') === 'none') return element;
+
+ if (element.parentNode && element.parentNode.style) {
+ return $.findElementWithDisplayNone(element.parentNode);
+ }
+
+ return null;
+};
+
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./environment.js')
+// tb_require('./dom.js')
+
+OTHelpers.Modal = function(options) {
+
+ OTHelpers.eventing(this, true);
+
+ var callback = arguments[arguments.length - 1];
+
+ if(!OTHelpers.isFunction(callback)) {
+ throw new Error('OTHelpers.Modal2 must be given a callback');
+ }
+
+ if(arguments.length < 2) {
+ options = {};
+ }
+
+ var domElement = document.createElement('iframe');
+
+ domElement.id = options.id || OTHelpers.uuid();
+ domElement.style.position = 'absolute';
+ domElement.style.position = 'fixed';
+ domElement.style.height = '100%';
+ domElement.style.width = '100%';
+ domElement.style.top = '0px';
+ domElement.style.left = '0px';
+ domElement.style.right = '0px';
+ domElement.style.bottom = '0px';
+ domElement.style.zIndex = 1000;
+ domElement.style.border = '0';
+
+ try {
+ domElement.style.backgroundColor = 'rgba(0,0,0,0.2)';
+ } catch (err) {
+ // Old IE browsers don't support rgba and we still want to show the upgrade message
+ // but we just make the background of the iframe completely transparent.
+ domElement.style.backgroundColor = 'transparent';
+ domElement.setAttribute('allowTransparency', 'true');
+ }
+
+ domElement.scrolling = 'no';
+ domElement.setAttribute('scrolling', 'no');
+
+ // This is necessary for IE, as it will not inherit it's doctype from
+ // the parent frame.
+ var frameContent = '' +
+ '' +
+ '' +
+ '';
+
+ var wrappedCallback = function() {
+ var doc = domElement.contentDocument || domElement.contentWindow.document;
+
+ if (OTHelpers.env.iframeNeedsLoad) {
+ doc.body.style.backgroundColor = 'transparent';
+ doc.body.style.border = 'none';
+
+ if (OTHelpers.env.name !== 'IE') {
+ // Skip this for IE as we use the bookmarklet workaround
+ // for THAT browser.
+ doc.open();
+ doc.write(frameContent);
+ doc.close();
+ }
+ }
+
+ callback(
+ domElement.contentWindow,
+ doc
+ );
+ };
+
+ document.body.appendChild(domElement);
+
+ if(OTHelpers.env.iframeNeedsLoad) {
+ if (OTHelpers.env.name === 'IE') {
+ // This works around some issues with IE and document.write.
+ // Basically this works by slightly abusing the bookmarklet/scriptlet
+ // functionality that all browsers support.
+ domElement.contentWindow.contents = frameContent;
+ /*jshint scripturl:true*/
+ domElement.src = 'javascript:window["contents"]';
+ /*jshint scripturl:false*/
+ }
+
+ OTHelpers.on(domElement, 'load', wrappedCallback);
+ } else {
+ setTimeout(wrappedCallback, 0);
+ }
+
+ this.close = function() {
+ OTHelpers.removeElement(domElement);
+ this.trigger('closed');
+ this.element = domElement = null;
+ return this;
+ };
+
+ this.element = domElement;
+
+};
+
+/*
+ * getComputedStyle from
+ * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+
+/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/
+
+(function() {
+
+ /*jshint eqnull: true, browser: true */
+
+
+ function getPixelSize(element, style, property, fontSize) {
+ var sizeWithSuffix = style[property],
+ size = parseFloat(sizeWithSuffix),
+ suffix = sizeWithSuffix.split(/\d/)[0],
+ rootSize;
+
+ fontSize = fontSize != null ?
+ fontSize : /%|em/.test(suffix) && element.parentElement ?
+ getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) :
+ 16;
+ rootSize = property === 'fontSize' ?
+ fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;
+
+ return (suffix === 'em') ?
+ size * fontSize : (suffix === 'in') ?
+ size * 96 : (suffix === 'pt') ?
+ size * 96 / 72 : (suffix === '%') ?
+ size / 100 * rootSize : size;
+ }
+
+ function setShortStyleProperty(style, property) {
+ var
+ borderSuffix = property === 'border' ? 'Width' : '',
+ t = property + 'Top' + borderSuffix,
+ r = property + 'Right' + borderSuffix,
+ b = property + 'Bottom' + borderSuffix,
+ l = property + 'Left' + borderSuffix;
+
+ style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]]
+ : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]]
+ : style[l] === style[r] ? [style[t], style[r], style[b]]
+ : [style[t], style[r], style[b], style[l]]).join(' ');
+ }
+
+ function CSSStyleDeclaration(element) {
+ var currentStyle = element.currentStyle,
+ style = this,
+ fontSize = getPixelSize(element, currentStyle, 'fontSize', null),
+ property;
+
+ for (property in currentStyle) {
+ if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') {
+ style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px';
+ } else if (property === 'styleFloat') {
+ /*jshint -W069 */
+ style['float'] = currentStyle[property];
+ } else {
+ style[property] = currentStyle[property];
+ }
+ }
+
+ setShortStyleProperty(style, 'margin');
+ setShortStyleProperty(style, 'padding');
+ setShortStyleProperty(style, 'border');
+
+ style.fontSize = fontSize + 'px';
+
+ return style;
+ }
+
+ CSSStyleDeclaration.prototype = {
+ constructor: CSSStyleDeclaration,
+ getPropertyPriority: function () {},
+ getPropertyValue: function ( prop ) {
+ return this[prop] || '';
+ },
+ item: function () {},
+ removeProperty: function () {},
+ setProperty: function () {},
+ getPropertyCSSValue: function () {}
+ };
+
+ function getComputedStyle(element) {
+ return new CSSStyleDeclaration(element);
+ }
+
+
+ OTHelpers.getComputedStyle = function(element) {
+ if(element &&
+ element.ownerDocument &&
+ element.ownerDocument.defaultView &&
+ element.ownerDocument.defaultView.getComputedStyle) {
+ return element.ownerDocument.defaultView.getComputedStyle(element);
+ } else {
+ return getComputedStyle(element);
+ }
+ };
+
+})();
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./callbacks.js')
+// tb_require('./dom.js')
+
+var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) {
+ var oldStyles = {};
+
+ var getStyle = function getStyle(style) {
+ switch (style) {
+ case 'width':
+ return $(element).width();
+
+ case 'height':
+ return $(element).height();
+
+ default:
+ return $(element).css(style);
+ }
+ };
+
+ // get the inital values
+ $.forEach(stylesToObserve, function(style) {
+ oldStyles[style] = getStyle(style);
+ });
+
+ var observer = new MutationObserver(function(mutations) {
+ var changeSet = {};
+
+ $.forEach(mutations, function(mutation) {
+ if (mutation.attributeName !== 'style') return;
+
+ var isHidden = $.isDisplayNone(element);
+
+ $.forEach(stylesToObserve, function(style) {
+ if(isHidden && (style === 'width' || style === 'height')) return;
+
+ var newValue = getStyle(style);
+
+ if (newValue !== oldStyles[style]) {
+ changeSet[style] = [oldStyles[style], newValue];
+ oldStyles[style] = newValue;
+ }
+ });
+ });
+
+ if (!$.isEmpty(changeSet)) {
+ // Do this after so as to help avoid infinite loops of mutations.
+ $.callAsync(function() {
+ onChange.call(null, changeSet);
+ });
+ }
+ });
+
+ observer.observe(element, {
+ attributes:true,
+ attributeFilter: ['style'],
+ childList:false,
+ characterData:false,
+ subtree:false
+ });
+
+ return observer;
+};
+
+var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) {
+ var observer = new MutationObserver(function(mutations) {
+ var removedNodes = [];
+
+ $.forEach(mutations, function(mutation) {
+ if (mutation.removedNodes.length) {
+ removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes));
+ }
+ });
+
+ if (removedNodes.length) {
+ // Do this after so as to help avoid infinite loops of mutations.
+ $.callAsync(function() {
+ onChange($(removedNodes));
+ });
+ }
+ });
+
+ observer.observe(element, {
+ attributes:false,
+ childList:true,
+ characterData:false,
+ subtree:true
+ });
+
+ return observer;
+};
+
+var observeSize = function (element, onChange) {
+ var previousSize = {
+ width: 0,
+ height: 0
+ };
+
+ var interval = setInterval(function() {
+ var rect = element.getBoundingClientRect();
+ if (previousSize.width !== rect.width || previousSize.height !== rect.height) {
+ onChange(rect, previousSize);
+ previousSize = {
+ width: rect.width,
+ height: rect.height
+ };
+ }
+ }, 1000 / 5);
+
+ return {
+ disconnect: function() {
+ clearInterval(interval);
+ }
+ };
+};
+
+// Allows an +onChange+ callback to be triggered when specific style properties
+// of +element+ are notified. The callback accepts a single parameter, which is
+// a hash where the keys are the style property that changed and the values are
+// an array containing the old and new values ([oldValue, newValue]).
+//
+// Width and Height changes while the element is display: none will not be
+// fired until such time as the element becomes visible again.
+//
+// This function returns the MutationObserver itself. Once you no longer wish
+// to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// dimensionsObserver = OTHelpers(object).observeStyleChanges(,
+// ['width', 'height'], function(changeSet) {
+// OT.debug("The new width and height are " +
+// changeSet.width[1] + ',' + changeSet.height[1]);
+// });
+//
+// Cleaning up
+// // stop observing changes
+// dimensionsObserver.disconnect();
+// dimensionsObserver = null;
+//
+ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeStyleChanges(element, stylesToObserve, onChange)
+ );
+ });
+
+ return observers;
+};
+
+// trigger the +onChange+ callback whenever
+// 1. +element+ is removed
+// 2. or an immediate child of +element+ is removed.
+//
+// This function returns the MutationObserver itself. Once you no longer wish
+// to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) {
+// OT.debug("Some child nodes were removed");
+// removedNodes.forEach(function(node) {
+// OT.debug(node);
+// });
+// });
+//
+// Cleaning up
+// // stop observing changes
+// nodeObserver.disconnect();
+// nodeObserver = null;
+//
+ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeNodeOrChildNodeRemoval(element, onChange)
+ );
+ });
+
+ return observers;
+};
+
+// trigger the +onChange+ callback whenever the width or the height of the element changes
+//
+// Once you no longer wish to observe the element you should call disconnect on the observer.
+//
+// Observing changes:
+// // observe changings to the width and height of object
+// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) {
+// OT.debug("The new width and height are " +
+// newSize.width + ',' + newSize.height);
+// });
+//
+// Cleaning up
+// // stop observing changes
+// sizeObserver.disconnect();
+// sizeObserver = null;
+//
+ElementCollection.prototype.observeSize = function(onChange) {
+ var observers = [];
+
+ this.forEach(function(element) {
+ observers.push(
+ observeSize(element, onChange)
+ );
+ });
+
+ return observers;
+};
+
+
+// @remove
+OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) {
+ return $(element).observeStyleChanges(stylesToObserve, onChange)[0];
+};
+
+// @remove
+OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) {
+ return $(element).observeNodeOrChildNodeRemoval(onChange)[0];
+};
+
+/*jshint browser:true, smarttabs:true */
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+// tb_require('./capabilities.js')
+
+// Returns true if the client supports element.classList
+OTHelpers.registerCapability('classList', function() {
+ return (typeof document !== 'undefined') && ('classList' in document.createElement('a'));
+});
+
+
+function hasClass (element, className) {
+ if (!className) return false;
+
+ if ($.hasCapabilities('classList')) {
+ return element.classList.contains(className);
+ }
+
+ return element.className.indexOf(className) > -1;
+}
+
+function toggleClasses (element, classNames) {
+ if (!classNames || classNames.length === 0) return;
+
+ // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
+ if (element.nodeType !== 1) {
+ return;
+ }
+
+ var numClasses = classNames.length,
+ i = 0;
+
+ if ($.hasCapabilities('classList')) {
+ for (; i 0) {
+ return element.offsetWidth + 'px';
+ }
+
+ return $(element).css('width');
+ },
+
+ _height = function(element) {
+ if (element.offsetHeight > 0) {
+ return element.offsetHeight + 'px';
+ }
+
+ return $(element).css('height');
+ };
+
+ ElementCollection.prototype.width = function (newWidth) {
+ if (newWidth) {
+ this.css('width', newWidth);
+ return this;
+ }
+ else {
+ if (this.isDisplayNone()) {
+ return this.makeVisibleAndYield(function(element) {
+ return _width(element);
+ })[0];
+ }
+ else {
+ return _width(this.get(0));
+ }
+ }
+ };
+
+ ElementCollection.prototype.height = function (newHeight) {
+ if (newHeight) {
+ this.css('height', newHeight);
+ return this;
+ }
+ else {
+ if (this.isDisplayNone()) {
+ // We can't get the height, probably since the element is hidden.
+ return this.makeVisibleAndYield(function(element) {
+ return _height(element);
+ })[0];
+ }
+ else {
+ return _height(this.get(0));
+ }
+ }
+ };
+
+ // @remove
+ OTHelpers.width = function(element, newWidth) {
+ var ret = $(element).width(newWidth);
+ return newWidth ? OTHelpers : ret;
+ };
+
+ // @remove
+ OTHelpers.height = function(element, newHeight) {
+ var ret = $(element).height(newHeight);
+ return newHeight ? OTHelpers : ret;
+ };
+
+})();
+
+
+// CSS helpers helpers
+
+/*jshint browser:true, smarttabs:true*/
+
+// tb_require('../helpers.js')
+// tb_require('./dom.js')
+// tb_require('./getcomputedstyle.js')
+
+(function() {
+
+ var displayStateCache = {},
+ defaultDisplays = {};
+
+ var defaultDisplayValueForElement = function (element) {
+ if (defaultDisplays[element.ownerDocument] &&
+ defaultDisplays[element.ownerDocument][element.nodeName]) {
+ return defaultDisplays[element.ownerDocument][element.nodeName];
+ }
+
+ if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {};
+
+ // We need to know what display value to use for this node. The easiest way
+ // is to actually create a node and read it out.
+ var testNode = element.ownerDocument.createElement(element.nodeName),
+ defaultDisplay;
+
+ element.ownerDocument.body.appendChild(testNode);
+ defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] =
+ $(testNode).css('display');
+
+ $(testNode).remove();
+ testNode = null;
+
+ return defaultDisplay;
+ };
+
+ var isHidden = function (element) {
+ var computedStyle = $.getComputedStyle(element);
+ return computedStyle.getPropertyValue('display') === 'none';
+ };
+
+ var setCssProperties = function (element, hash) {
+ var style = element.style;
+
+ for (var cssName in hash) {
+ if (hash.hasOwnProperty(cssName)) {
+ style[cssName] = hash[cssName];
+ }
+ }
+ };
+
+ var setCssProperty = function (element, name, value) {
+ element.style[name] = value;
+ };
+
+ var getCssProperty = function (element, unnormalisedName) {
+ // Normalise vendor prefixes from the form MozTranform to -moz-transform
+ // except for ms extensions, which are weird...
+
+ var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(),
+ computedStyle = $.getComputedStyle(element),
+ currentValue = computedStyle.getPropertyValue(name);
+
+ if (currentValue === '') {
+ currentValue = element.style[name];
+ }
+
+ return currentValue;
+ };
+
+ var applyCSS = function(element, styles, callback) {
+ var oldStyles = {},
+ name,
+ ret;
+
+ // Backup the old styles
+ for (name in styles) {
+ if (styles.hasOwnProperty(name)) {
+ // We intentionally read out of style here, instead of using the css
+ // helper. This is because the css helper uses querySelector and we
+ // only want to pull values out of the style (domeElement.style) hash.
+ oldStyles[name] = element.style[name];
+
+ $(element).css(name, styles[name]);
+ }
+ }
+
+ ret = callback(element);
+
+ // Restore the old styles
+ for (name in styles) {
+ if (styles.hasOwnProperty(name)) {
+ $(element).css(name, oldStyles[name] || '');
+ }
+ }
+
+ return ret;
+ };
+
+ ElementCollection.prototype.show = function() {
+ return this.forEach(function(element) {
+ var display = element.style.display;
+
+ if (display === '' || display === 'none') {
+ element.style.display = displayStateCache[element] || '';
+ delete displayStateCache[element];
+ }
+
+ if (isHidden(element)) {
+ // It's still hidden so there's probably a stylesheet that declares this
+ // element as display:none;
+ displayStateCache[element] = 'none';
+
+ element.style.display = defaultDisplayValueForElement(element);
+ }
+ });
+ };
+
+ ElementCollection.prototype.hide = function() {
+ return this.forEach(function(element) {
+ if (element.style.display === 'none') return;
+
+ displayStateCache[element] = element.style.display;
+ element.style.display = 'none';
+ });
+ };
+
+ ElementCollection.prototype.css = function(nameOrHash, value) {
+ if (this.length === 0) return;
+
+ if (typeof(nameOrHash) !== 'string') {
+
+ return this.forEach(function(element) {
+ setCssProperties(element, nameOrHash);
+ });
+
+ } else if (value !== undefined) {
+
+ return this.forEach(function(element) {
+ setCssProperty(element, nameOrHash, value);
+ });
+
+ } else {
+ return getCssProperty(this.first, nameOrHash, value);
+ }
+ };
+
+ // Apply +styles+ to +element+ while executing +callback+, restoring the previous
+ // styles after the callback executes.
+ ElementCollection.prototype.applyCSS = function (styles, callback) {
+ var results = [];
+
+ this.forEach(function(element) {
+ results.push(applyCSS(element, styles, callback));
+ });
+
+ return results;
+ };
+
+
+ // Make +element+ visible while executing +callback+.
+ ElementCollection.prototype.makeVisibleAndYield = function (callback) {
+ var hiddenVisually = {
+ display: 'block',
+ visibility: 'hidden'
+ },
+ results = [];
+
+ this.forEach(function(element) {
+ // find whether it's the element or an ancestor that's display none and
+ // then apply to whichever it is
+ var targetElement = $.findElementWithDisplayNone(element);
+ if (!targetElement) {
+ results.push(void 0);
+ }
+ else {
+ results.push(
+ applyCSS(targetElement, hiddenVisually, callback)
+ );
+ }
+ });
+
+ return results;
+ };
+
+
+ // @remove
+ OTHelpers.show = function(element) {
+ return $(element).show();
+ };
+
+ // @remove
+ OTHelpers.hide = function(element) {
+ return $(element).hide();
+ };
+
+ // @remove
+ OTHelpers.css = function(element, nameOrHash, value) {
+ return $(element).css(nameOrHash, value);
+ };
+
+ // @remove
+ OTHelpers.applyCSS = function(element, styles, callback) {
+ return $(element).applyCSS(styles, callback);
+ };
+
+ // @remove
+ OTHelpers.makeVisibleAndYield = function(element, callback) {
+ return $(element).makeVisibleAndYield(callback);
+ };
+
+})();
+
// tb_require('../helpers.js')
/*!
@@ -2445,1685 +5725,6 @@ OTHelpers.statable = function(self, possibleStates, initialState, stateChanged,
}).call(this);
/* jshint ignore:end */
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-
-
-var getErrorLocation;
-
-// Properties that we'll acknowledge from the JS Error object
-var safeErrorProps = [
- 'description',
- 'fileName',
- 'lineNumber',
- 'message',
- 'name',
- 'number',
- 'stack'
-];
-
-
-// OTHelpers.Error
-//
-// A construct to contain error information that also helps with extracting error
-// context, such as stack trace.
-//
-// @constructor
-// @memberof OTHelpers
-// @method Error
-//
-// @param {String} message
-// Optional. The error message
-//
-// @param {Object} props
-// Optional. A dictionary of properties containing extra Error info.
-//
-//
-// @example Create a simple error with juts a custom message
-// var error = new OTHelpers.Error('Something Broke!');
-// error.message === 'Something Broke!';
-//
-// @example Create an Error with a message and a name
-// var error = new OTHelpers.Error('Something Broke!', 'FooError');
-// error.message === 'Something Broke!';
-// error.name === 'FooError';
-//
-// @example Create an Error with a message, name, and custom properties
-// var error = new OTHelpers.Error('Something Broke!', 'FooError', {
-// foo: 'bar',
-// listOfImportantThings: [1,2,3,4]
-// });
-// error.message === 'Something Broke!';
-// error.name === 'FooError';
-// error.foo === 'bar';
-// error.listOfImportantThings == [1,2,3,4];
-//
-// @example Create an Error from a Javascript Error
-// var error = new OTHelpers.Error(domSyntaxError);
-// error.message === domSyntaxError.message;
-// error.name === domSyntaxError.name === 'SyntaxError';
-// // ...continues for each properties of domSyntaxError
-//
-// @references
-// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
-// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
-// * http://www.w3.org/TR/dom/#interface-domerror
-//
-//
-// @todo
-// * update usage in OTMedia
-// * replace error handling in OT.js
-// * normalise stack behaviour under Chrome/Node/Safari with other browsers
-// * unit test for stack parsing
-//
-//
-OTHelpers.Error = function (message, name, props) {
- switch (arguments.length) {
- case 1:
- if ($.isObject(message)) {
- props = message;
- name = void 0;
- message = void 0;
- }
- // Otherwise it's the message
- break;
-
- case 2:
- if ($.isObject(name)) {
- props = name;
- name = void 0;
- }
- // Otherwise name is actually the name
-
- break;
- }
-
- if ( props instanceof Error) {
- // Special handling of this due to Chrome weirdness. It seems that
- // properties of the Error object, and it's children, are not
- // enumerable in Chrome?
- for (var i = 0, num = safeErrorProps.length; i < num; ++i) {
- this[safeErrorProps[i]] = props[safeErrorProps[i]];
- }
- }
- else if ( $.isObject(props)) {
- // Use an custom properties that are provided
- for (var key in props) {
- if (props.hasOwnProperty(key)) {
- this[key] = props[key];
- }
- }
- }
-
- // If any of the fundamental properties are missing then try and
- // extract them.
- if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) {
- var err = getErrorLocation();
-
- if (!this.fileName && err.fileName) {
- this.fileName = err.fileName;
- }
-
- if (!this.lineNumber && err.lineNumber) {
- this.lineNumber = err.lineNumber;
- }
-
- if (!this.columnNumber && err.columnNumber) {
- this.columnNumber = err.columnNumber;
- }
-
- if (!this.stack && err.stack) {
- this.stack = err.stack;
- }
- }
-
- if (!this.message && message) this.message = message;
- if (!this.name && name) this.name = name;
-};
-
-OTHelpers.Error.prototype.toString =
-OTHelpers.Error.prototype.valueOf = function() {
- var locationDetails = '';
- if (this.fileName) locationDetails += ' ' + this.fileName;
- if (this.lineNumber) {
- locationDetails += ' ' + this.lineNumber;
- if (this.columnNumber) locationDetails += ':' + this.columnNumber;
- }
-
- return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>';
-};
-
-
-// Normalise err.stack so that it is the same format as the other browsers
-// We skip the first two frames so that we don't capture getErrorLocation() and
-// the callee.
-//
-// Used by Environments that support the StackTrace API. (Chrome, Node, Opera)
-//
-var prepareStackTrace = function prepareStackTrace (_, stack){
- return $.map(stack.slice(2), function(frame) {
- var _f = {
- fileName: frame.getFileName(),
- linenumber: frame.getLineNumber(),
- columnNumber: frame.getColumnNumber()
- };
-
- if (frame.getFunctionName()) _f.functionName = frame.getFunctionName();
- if (frame.getMethodName()) _f.methodName = frame.getMethodName();
- if (frame.getThis()) _f.self = frame.getThis();
-
- return _f;
- });
-};
-
-
-// Black magic to retrieve error location info for various environments
-getErrorLocation = function getErrorLocation () {
- var info = {},
- callstack,
- errLocation,
- err;
-
- switch ($.env.name) {
- case 'Firefox':
- case 'Safari':
- case 'IE':
-
- if ($.env.name === 'IE') {
- err = new Error();
- }
- else {
- try {
- window.call.js.is.explody;
- }
- catch(e) { err = e; }
- }
-
- callstack = err.stack.split('\n');
-
- //Remove call to getErrorLocation() and the callee
- callstack.shift();
- callstack.shift();
-
- info.stack = callstack;
-
- if ($.env.name === 'IE') {
- // IE also includes the error message in it's stack trace
- info.stack.shift();
-
- // each line begins with some amounts of spaces and 'at', we remove
- // these to normalise with the other browsers.
- info.stack = $.map(callstack, function(call) {
- return call.replace(/^\s+at\s+/g, '');
- });
- }
-
- errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]);
- if (errLocation) {
- info.fileName = errLocation[1];
- info.lineNumber = parseInt(errLocation[2], 10);
- if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10);
- }
- break;
-
- case 'Chrome':
- case 'Node':
- case 'Opera':
- var currentPST = Error.prepareStackTrace;
- Error.prepareStackTrace = prepareStackTrace;
- err = new Error();
- info.stack = err.stack;
- Error.prepareStackTrace = currentPST;
-
- var topFrame = info.stack[0];
- info.lineNumber = topFrame.lineNumber;
- info.columnNumber = topFrame.columnNumber;
- info.fileName = topFrame.fileName;
- if (topFrame.functionName) info.functionName = topFrame.functionName;
- if (topFrame.methodName) info.methodName = topFrame.methodName;
- if (topFrame.self) info.self = topFrame.self;
- break;
-
- default:
- err = new Error();
- if (err.stack) info.stack = err.stack.split('\n');
- break;
- }
-
- if (err.message) info.message = err.message;
- return info;
-};
-
-
-/*jshint browser:true, smarttabs:true*/
-/* global process */
-
-// tb_require('../helpers.js')
-
-
-// OTHelpers.env
-//
-// Contains information about the current environment.
-// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc)
-// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String
-// * **OTHelpers.env.userAgent** The raw user agent
-// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the
-// current version is greater than the argument
-//
-// Example
-// if (OTHelpers.env.versionGreaterThan('0.10.30')) {
-// // do something
-// }
-//
-(function() {
- // @todo make exposing userAgent unnecessary
- var version = -1;
-
- // Returns true if otherVersion is greater than the current environment
- // version.
- var versionGEThan = function versionGEThan (otherVersion) {
- if (otherVersion === version) return true;
-
- if (typeof(otherVersion) === 'number' && typeof(version) === 'number') {
- return otherVersion > version;
- }
-
- // The versions have multiple components (i.e. 0.10.30) and
- // must be compared piecewise.
- // Note: I'm ignoring the case where one version has multiple
- // components and the other doesn't.
- var v1 = otherVersion.split('.'),
- v2 = version.split('.'),
- versionLength = (v1.length > v2.length ? v2 : v1).length;
-
- for (var i = 0; i < versionLength; ++i) {
- if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
- return true;
- }
- }
-
- // Special case, v1 has extra components but the initial components
- // were identical, we assume this means newer but it might also mean
- // that someone changed versioning systems.
- if (i < v1.length) {
- return true;
- }
-
- return false;
- };
-
- var env = function() {
- if (typeof(process) !== 'undefined' &&
- typeof(process.versions) !== 'undefined' &&
- typeof(process.versions.node) === 'string') {
-
- version = process.versions.node;
- if (version.substr(1) === 'v') version = version.substr(1);
-
- // Special casing node to avoid gating window.navigator.
- // Version will be a string rather than a float.
- return {
- name: 'Node',
- version: version,
- userAgent: 'Node ' + version,
- iframeNeedsLoad: false,
- versionGreaterThan: versionGEThan
- };
- }
-
- var userAgent = window.navigator.userAgent.toLowerCase(),
- appName = window.navigator.appName,
- navigatorVendor,
- name = 'unknown';
-
- if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) {
- name = 'Opera';
-
- if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (userAgent.indexOf('firefox') > -1) {
- name = 'Firefox';
-
- if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (appName === 'Microsoft Internet Explorer') {
- // IE 10 and below
- name = 'IE';
-
- if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) {
- // IE 11+
-
- name = 'IE';
-
- if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if (userAgent.indexOf('chrome') > -1) {
- name = 'Chrome';
-
- if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
-
- } else if ((navigatorVendor = window.navigator.vendor) &&
- navigatorVendor.toLowerCase().indexOf('apple') > -1) {
- name = 'Safari';
-
- if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) {
- version = parseFloat( RegExp.$1 );
- }
- }
-
- return {
- name: name,
- version: version,
- userAgent: window.navigator.userAgent,
- iframeNeedsLoad: userAgent.indexOf('webkit') < 0,
- versionGreaterThan: versionGEThan
- };
- }();
-
-
- OTHelpers.env = env;
-
- OTHelpers.browser = function() {
- return OTHelpers.env.name;
- };
-
- OTHelpers.browserVersion = function() {
- return OTHelpers.env;
- };
-
-})();
-// tb_require('../../environment.js')
-// tb_require('./event.js')
-
-var nodeEventing;
-
-if($.env.name === 'Node') {
- (function() {
- var EventEmitter = require('events').EventEmitter,
- util = require('util');
-
- // container for the EventEmitter behaviour. This prevents tight coupling
- // caused by accidentally bleeding implementation details and API into whatever
- // objects nodeEventing is applied to.
- var NodeEventable = function NodeEventable () {
- EventEmitter.call(this);
-
- this.events = {};
- };
- util.inherits(NodeEventable, EventEmitter);
-
-
- nodeEventing = function nodeEventing (/* self */) {
- var api = new NodeEventable(),
- _on = api.on,
- _off = api.removeListener;
-
-
- api.addListeners = function (eventNames, handler, context, closure) {
- var listener = {handler: handler};
- if (context) listener.context = context;
- if (closure) listener.closure = closure;
-
- $.forEach(eventNames, function(name) {
- if (!api.events[name]) api.events[name] = [];
- api.events[name].push(listener);
-
- _on(name, handler);
-
- var addedListener = name + ':added';
- if (api.events[addedListener]) {
- api.emit(addedListener, api.events[name].length);
- }
- });
- };
-
- api.removeAllListenersNamed = function (eventNames) {
- var _eventNames = eventNames.split(' ');
- api.removeAllListeners(_eventNames);
-
- $.forEach(_eventNames, function(name) {
- if (api.events[name]) delete api.events[name];
- });
- };
-
- api.removeListeners = function (eventNames, handler, closure) {
- function filterHandlers(listener) {
- return !(listener.handler === handler && listener.closure === closure);
- }
-
- $.forEach(eventNames.split(' '), function(name) {
- if (api.events[name]) {
- _off(name, handler);
- api.events[name] = $.filter(api.events[name], filterHandlers);
- if (api.events[name].length === 0) delete api.events[name];
-
- var removedListener = name + ':removed';
- if (api.events[removedListener]) {
- api.emit(removedListener, api.events[name] ? api.events[name].length : 0);
- }
- }
- });
- };
-
- api.removeAllListeners = function () {
- api.events = {};
- api.removeAllListeners();
- };
-
- api.dispatchEvent = function(event, defaultAction) {
- this.emit(event.type, event);
-
- if (defaultAction) {
- defaultAction.call(null, event);
- }
- };
-
- api.trigger = $.bind(api.emit, api);
-
-
- return api;
- };
- })();
-}
-
-// tb_require('../../environment.js')
-// tb_require('./event.js')
-
-var browserEventing;
-
-if($.env.name !== 'Node') {
-
- browserEventing = function browserEventing (self, syncronous) {
- var api = {
- events: {}
- };
-
-
- // Call the defaultAction, passing args
- function executeDefaultAction(defaultAction, args) {
- if (!defaultAction) return;
-
- defaultAction.apply(null, args.slice());
- }
-
- // Execute each handler in +listeners+ with +args+.
- //
- // Each handler will be executed async. On completion the defaultAction
- // handler will be executed with the args.
- //
- // @param [Array] listeners
- // An array of functions to execute. Each will be passed args.
- //
- // @param [Array] args
- // An array of arguments to execute each function in +listeners+ with.
- //
- // @param [String] name
- // The name of this event.
- //
- // @param [Function, Null, Undefined] defaultAction
- // An optional function to execute after every other handler. This will execute even
- // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
- // handler would.
- //
- // @return Undefined
- //
- function executeListenersAsyncronously(name, args, defaultAction) {
- var listeners = api.events[name];
- if (!listeners || listeners.length === 0) return;
-
- var listenerAcks = listeners.length;
-
- $.forEach(listeners, function(listener) { // , index
- function filterHandlers(_listener) {
- return _listener.handler === listener.handler;
- }
-
- // We run this asynchronously so that it doesn't interfere with execution if an
- // error happens
- $.callAsync(function() {
- try {
- // have to check if the listener has not been removed
- if (api.events[name] && $.some(api.events[name], filterHandlers)) {
- (listener.closure || listener.handler).apply(listener.context || null, args);
- }
- }
- finally {
- listenerAcks--;
-
- if (listenerAcks === 0) {
- executeDefaultAction(defaultAction, args);
- }
- }
- });
- });
- }
-
-
- // This is identical to executeListenersAsyncronously except that handlers will
- // be executed syncronously.
- //
- // On completion the defaultAction handler will be executed with the args.
- //
- // @param [Array] listeners
- // An array of functions to execute. Each will be passed args.
- //
- // @param [Array] args
- // An array of arguments to execute each function in +listeners+ with.
- //
- // @param [String] name
- // The name of this event.
- //
- // @param [Function, Null, Undefined] defaultAction
- // An optional function to execute after every other handler. This will execute even
- // if +listeners+ is empty. +defaultAction+ will be passed args as a normal
- // handler would.
- //
- // @return Undefined
- //
- function executeListenersSyncronously(name, args) { // defaultAction is not used
- var listeners = api.events[name];
- if (!listeners || listeners.length === 0) return;
-
- $.forEach(listeners, function(listener) { // index
- (listener.closure || listener.handler).apply(listener.context || null, args);
- });
- }
-
- var executeListeners = syncronous === true ?
- executeListenersSyncronously : executeListenersAsyncronously;
-
-
- api.addListeners = function (eventNames, handler, context, closure) {
- var listener = {handler: handler};
- if (context) listener.context = context;
- if (closure) listener.closure = closure;
-
- $.forEach(eventNames, function(name) {
- if (!api.events[name]) api.events[name] = [];
- api.events[name].push(listener);
-
- var addedListener = name + ':added';
- if (api.events[addedListener]) {
- executeListeners(addedListener, [api.events[name].length]);
- }
- });
- };
-
- api.removeListeners = function(eventNames, handler, context) {
- function filterListeners(listener) {
- var isCorrectHandler = (
- listener.handler.originalHandler === handler ||
- listener.handler === handler
- );
-
- return !(isCorrectHandler && listener.context === context);
- }
-
- $.forEach(eventNames, function(name) {
- if (api.events[name]) {
- api.events[name] = $.filter(api.events[name], filterListeners);
- if (api.events[name].length === 0) delete api.events[name];
-
- var removedListener = name + ':removed';
- if (api.events[ removedListener]) {
- executeListeners(removedListener, [api.events[name] ? api.events[name].length : 0]);
- }
- }
- });
- };
-
- api.removeAllListenersNamed = function (eventNames) {
- $.forEach(eventNames, function(name) {
- if (api.events[name]) {
- delete api.events[name];
- }
- });
- };
-
- api.removeAllListeners = function () {
- api.events = {};
- };
-
- api.dispatchEvent = function(event, defaultAction) {
- if (!api.events[event.type] || api.events[event.type].length === 0) {
- executeDefaultAction(defaultAction, [event]);
- return;
- }
-
- executeListeners(event.type, [event], defaultAction);
- };
-
- api.trigger = function(eventName, args) {
- if (!api.events[eventName] || api.events[eventName].length === 0) {
- return;
- }
-
- executeListeners(eventName, args);
- };
-
-
- return api;
- };
-}
-
-/*jshint browser:false, smarttabs:true*/
-/* global window, require */
-
-// tb_require('../../helpers.js')
-// tb_require('../environment.js')
-
-if (window.OTHelpers.env.name === 'Node') {
- var request = require('request');
-
- OTHelpers.request = function(url, options, callback) {
- var completion = function(error, response, body) {
- var event = {response: response, body: body};
-
- // We need to detect things that Request considers a success,
- // but we consider to be failures.
- if (!error && response.statusCode >= 200 &&
- (response.statusCode < 300 || response.statusCode === 304) ) {
- callback(null, event);
- } else {
- callback(error, event);
- }
- };
-
- if (options.method.toLowerCase() === 'get') {
- request.get(url, completion);
- }
- else {
- request.post(url, options.body, completion);
- }
- };
-
- OTHelpers.getJSON = function(url, options, callback) {
- var extendedHeaders = require('underscore').extend(
- {
- 'Accept': 'application/json'
- },
- options.headers || {}
- );
-
- request.get({
- url: url,
- headers: extendedHeaders,
- json: true
- }, function(err, response) {
- callback(err, response && response.body);
- });
- };
-}
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../../helpers.js')
-// tb_require('../environment.js')
-
-function formatPostData(data) { //, contentType
- // If it's a string, we assume it's properly encoded
- if (typeof(data) === 'string') return data;
-
- var queryString = [];
-
- for (var key in data) {
- queryString.push(
- encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
- );
- }
-
- return queryString.join('&').replace(/\+/g, '%20');
-}
-
-if (window.OTHelpers.env.name !== 'Node') {
-
- OTHelpers.xdomainRequest = function(url, options, callback) {
- /*global XDomainRequest*/
- var xdr = new XDomainRequest(),
- _options = options || {},
- _method = _options.method.toLowerCase();
-
- if(!_method) {
- callback(new Error('No HTTP method specified in options'));
- return;
- }
-
- _method = _method.toUpperCase();
-
- if(!(_method === 'GET' || _method === 'POST')) {
- callback(new Error('HTTP method can only be '));
- return;
- }
-
- function done(err, event) {
- xdr.onload = xdr.onerror = xdr.ontimeout = function() {};
- xdr = void 0;
- callback(err, event);
- }
-
-
- xdr.onload = function() {
- done(null, {
- target: {
- responseText: xdr.responseText,
- headers: {
- 'content-type': xdr.contentType
- }
- }
- });
- };
-
- xdr.onerror = function() {
- done(new Error('XDomainRequest of ' + url + ' failed'));
- };
-
- xdr.ontimeout = function() {
- done(new Error('XDomainRequest of ' + url + ' timed out'));
- };
-
- xdr.open(_method, url);
- xdr.send(options.body && formatPostData(options.body));
-
- };
-
- OTHelpers.request = function(url, options, callback) {
- var request = new XMLHttpRequest(),
- _options = options || {},
- _method = _options.method;
-
- if(!_method) {
- callback(new Error('No HTTP method specified in options'));
- return;
- }
-
- // Setup callbacks to correctly respond to success and error callbacks. This includes
- // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore
- // by default.
- if(callback) {
- OTHelpers.on(request, 'load', function(event) {
- var status = event.target.status;
-
- // We need to detect things that XMLHttpRequest considers a success,
- // but we consider to be failures.
- if ( status >= 200 && (status < 300 || status === 304) ) {
- callback(null, event);
- } else {
- callback(event);
- }
- });
-
- OTHelpers.on(request, 'error', callback);
- }
-
- request.open(options.method, url, true);
-
- if (!_options.headers) _options.headers = {};
-
- for (var name in _options.headers) {
- request.setRequestHeader(name, _options.headers[name]);
- }
-
- request.send(options.body && formatPostData(options.body));
- };
-
-
- OTHelpers.getJSON = function(url, options, callback) {
- options = options || {};
-
- var done = function(error, event) {
- if(error) {
- callback(error, event && event.target && event.target.responseText);
- } else {
- var response;
-
- try {
- response = JSON.parse(event.target.responseText);
- } catch(e) {
- // Badly formed JSON
- callback(e, event && event.target && event.target.responseText);
- return;
- }
-
- callback(null, response, event);
- }
- };
-
- if(options.xdomainrequest) {
- OTHelpers.xdomainRequest(url, { method: 'GET' }, done);
- } else {
- var extendedHeaders = OTHelpers.extend({
- 'Accept': 'application/json'
- }, options.headers || {});
-
- OTHelpers.get(url, OTHelpers.extend(options || {}, {
- headers: extendedHeaders
- }), done);
- }
-
- };
-
-}
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./environment.js')
-
-
-// Log levels for OTLog.setLogLevel
-var LOG_LEVEL_DEBUG = 5,
- LOG_LEVEL_LOG = 4,
- LOG_LEVEL_INFO = 3,
- LOG_LEVEL_WARN = 2,
- LOG_LEVEL_ERROR = 1,
- LOG_LEVEL_NONE = 0;
-
-
-// There is a single global log level for every component that uses
-// the logs.
-var _logLevel = LOG_LEVEL_NONE;
-
-var setLogLevel = function setLogLevel (level) {
- _logLevel = typeof(level) === 'number' ? level : 0;
- return _logLevel;
-};
-
-
-OTHelpers.useLogHelpers = function(on){
-
- // Log levels for OTLog.setLogLevel
- on.DEBUG = LOG_LEVEL_DEBUG;
- on.LOG = LOG_LEVEL_LOG;
- on.INFO = LOG_LEVEL_INFO;
- on.WARN = LOG_LEVEL_WARN;
- on.ERROR = LOG_LEVEL_ERROR;
- on.NONE = LOG_LEVEL_NONE;
-
- var _logs = [],
- _canApplyConsole = true;
-
- try {
- Function.prototype.bind.call(window.console.log, window.console);
- } catch (err) {
- _canApplyConsole = false;
- }
-
- // Some objects can't be logged in the console, mostly these are certain
- // types of native objects that are exposed to JS. This is only really a
- // problem with IE, hence only the IE version does anything.
- var makeLogArgumentsSafe = function(args) { return args; };
-
- if (OTHelpers.env.name === 'IE') {
- makeLogArgumentsSafe = function(args) {
- return [toDebugString(prototypeSlice.apply(args))];
- };
- }
-
- // Generates a logging method for a particular method and log level.
- //
- // Attempts to handle the following cases:
- // * the desired log method doesn't exist, call fallback (if available) instead
- // * the console functionality isn't available because the developer tools (in IE)
- // aren't open, call fallback (if available)
- // * attempt to deal with weird IE hosted logging methods as best we can.
- //
- function generateLoggingMethod(method, level, fallback) {
- return function() {
- if (on.shouldLog(level)) {
- var cons = window.console,
- args = makeLogArgumentsSafe(arguments);
-
- // In IE, window.console may not exist if the developer tools aren't open
- // This also means that cons and cons[method] can appear at any moment
- // hence why we retest this every time.
- if (cons && cons[method]) {
- // the desired console method isn't a real object, which means
- // that we can't use apply on it. We force it to be a real object
- // using Function.bind, assuming that's available.
- if (cons[method].apply || _canApplyConsole) {
- if (!cons[method].apply) {
- cons[method] = Function.prototype.bind.call(cons[method], cons);
- }
-
- cons[method].apply(cons, args);
- }
- else {
- // This isn't the same result as the above, but it's better
- // than nothing.
- cons[method](args);
- }
- }
- else if (fallback) {
- fallback.apply(on, args);
-
- // Skip appendToLogs, we delegate entirely to the fallback
- return;
- }
-
- appendToLogs(method, makeLogArgumentsSafe(arguments));
- }
- };
- }
-
- on.log = generateLoggingMethod('log', on.LOG);
-
- // Generate debug, info, warn, and error logging methods, these all fallback to on.log
- on.debug = generateLoggingMethod('debug', on.DEBUG, on.log);
- on.info = generateLoggingMethod('info', on.INFO, on.log);
- on.warn = generateLoggingMethod('warn', on.WARN, on.log);
- on.error = generateLoggingMethod('error', on.ERROR, on.log);
-
-
- on.setLogLevel = function(level) {
- on.debug('TB.setLogLevel(' + _logLevel + ')');
- return setLogLevel(level);
- };
-
- on.getLogs = function() {
- return _logs;
- };
-
- // Determine if the level is visible given the current logLevel.
- on.shouldLog = function(level) {
- return _logLevel >= level;
- };
-
- // Format the current time nicely for logging. Returns the current
- // local time.
- function formatDateStamp() {
- var now = new Date();
- return now.toLocaleTimeString() + now.getMilliseconds();
- }
-
- function toJson(object) {
- try {
- return JSON.stringify(object);
- } catch(e) {
- return object.toString();
- }
- }
-
- function toDebugString(object) {
- var components = [];
-
- if (typeof(object) === 'undefined') {
- // noop
- }
- else if (object === null) {
- components.push('NULL');
- }
- else if (OTHelpers.isArray(object)) {
- for (var i=0; i 0) {
- var args = timeouts.shift(),
- fn = args.shift();
-
- fn.apply(null, args);
- }
- }
- };
-
- // Ensure that we don't receive messages after unload
- // Yes, this seems to really happen in IE sometimes, usually
- // when iFrames are involved.
- OTHelpers.on(window, 'unload', removeMessageHandler);
-
- if(window.addEventListener) {
- window.addEventListener('message', handleMessage, true);
- } else if(window.attachEvent) {
- window.attachEvent('onmessage', handleMessage);
- }
-
- _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
- timeouts.push(prototypeSlice.call(arguments));
- window.postMessage(messageName, '*');
- };
- }
- else {
- _callAsync = function (/* fn, [arg1, arg2, ..., argN] */) {
- var args = prototypeSlice.call(arguments),
- fn = args.shift();
-
- setTimeout(function() {
- fn.apply(null, args);
- }, 0);
- };
- }
-
-
- // Calls the function +fn+ asynchronously with the current execution.
- // This is most commonly used to execute something straight after
- // the current function.
- //
- // Any arguments in addition to +fn+ will be passed to +fn+ when it's
- // called.
- //
- // You would use this inplace of setTimeout(fn, 0) type constructs. callAsync
- // is preferable as it executes in a much more predictable time window,
- // unlike setTimeout which could execute anywhere from 2ms to several thousand
- // depending on the browser/context.
- //
- // It does this using window.postMessage, although if postMessage won't
- // work it will fallback to setTimeout.
- //
- OTHelpers.callAsync = _callAsync;
-
-
- // Wraps +handler+ in a function that will execute it asynchronously
- // so that it doesn't interfere with it's exceution context if it raises
- // an exception.
- OTHelpers.createAsyncHandler = function(handler) {
- return function() {
- var args = prototypeSlice.call(arguments);
-
- OTHelpers.callAsync(function() {
- handler.apply(null, args);
- });
- };
- };
-
-})();
-
/*global nodeEventing:true, browserEventing:true */
// tb_require('../../helpers.js')
@@ -4534,1583 +6135,17 @@ OTHelpers.eventing = function(self, syncronous) {
return self;
};
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-// tb_require('./dom_events.js')
-
-OTHelpers.createElement = function(nodeName, attributes, children, doc) {
- var element = (doc || document).createElement(nodeName);
-
- if (attributes) {
- for (var name in attributes) {
- if (typeof(attributes[name]) === 'object') {
- if (!element[name]) element[name] = {};
-
- var subAttrs = attributes[name];
- for (var n in subAttrs) {
- element[name][n] = subAttrs[n];
- }
- }
- else if (name === 'className') {
- element.className = attributes[name];
- }
- else {
- element.setAttribute(name, attributes[name]);
- }
- }
- }
-
- var setChildren = function(child) {
- if(typeof child === 'string') {
- element.innerHTML = element.innerHTML + child;
- } else {
- element.appendChild(child);
- }
- };
-
- if($.isArray(children)) {
- $.forEach(children, setChildren);
- } else if(children) {
- setChildren(children);
- }
-
- return element;
-};
-
-OTHelpers.createButton = function(innerHTML, attributes, events) {
- var button = $.createElement('button', attributes, innerHTML);
-
- if (events) {
- for (var name in events) {
- if (events.hasOwnProperty(name)) {
- $.on(button, name, events[name]);
- }
- }
-
- button._boundEvents = events;
- }
-
- return button;
-};
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-
-// DOM helpers
-
-var firstElementChild;
-
-// This mess is for IE8
-if( typeof(document) !== 'undefined' &&
- document.createElement('div').firstElementChild !== void 0 ){
- firstElementChild = function firstElementChild (parentElement) {
- return parentElement.firstElementChild;
- };
-}
-else {
- firstElementChild = function firstElementChild (parentElement) {
- var el = parentElement.firstChild;
-
- do {
- if(el.nodeType===1){
- return el;
- }
- el = el.nextSibling;
- } while(el);
-
- return null;
- };
-}
-
-
-ElementCollection.prototype.appendTo = function(parentElement) {
- if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.');
-
- return this.forEach(function(child) {
- parentElement.appendChild(child);
- });
-};
-
-ElementCollection.prototype.append = function() {
- var parentElement = this.first;
- if (!parentElement) return this;
-
- $.forEach(prototypeSlice.call(arguments), function(child) {
- parentElement.appendChild(child);
- });
-
- return this;
-};
-
-ElementCollection.prototype.prepend = function() {
- if (arguments.length === 0) return this;
-
- var parentElement = this.first,
- elementsToPrepend;
-
- if (!parentElement) return this;
-
- elementsToPrepend = prototypeSlice.call(arguments);
-
- if (!firstElementChild(parentElement)) {
- parentElement.appendChild(elementsToPrepend.shift());
- }
-
- $.forEach(elementsToPrepend, function(element) {
- parentElement.insertBefore(element, firstElementChild(parentElement));
- });
-
- return this;
-};
-
-ElementCollection.prototype.after = function(prevElement) {
- if (!prevElement) throw new Error('after requires a DOMElement to insert after');
-
- return this.forEach(function(element) {
- if (element.parentElement) {
- if (prevElement !== element.parentNode.lastChild) {
- element.parentElement.insertBefore(element, prevElement);
- }
- else {
- element.parentElement.appendChild(element);
- }
- }
- });
-};
-
-ElementCollection.prototype.before = function(nextElement) {
- if (!nextElement) {
- throw new Error('before requires a DOMElement to insert before');
- }
-
- return this.forEach(function(element) {
- if (element.parentElement) {
- element.parentElement.insertBefore(element, nextElement);
- }
- });
-};
-
-ElementCollection.prototype.remove = function () {
- return this.forEach(function(element) {
- if (element.parentNode) {
- element.parentNode.removeChild(element);
- }
- });
-};
-
-ElementCollection.prototype.empty = function () {
- return this.forEach(function(element) {
- // elements is a "live" NodesList collection. Meaning that the collection
- // itself will be mutated as we remove elements from the DOM. This means
- // that "while there are still elements" is safer than "iterate over each
- // element" as the collection length and the elements indices will be modified
- // with each iteration.
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
- });
-};
-
-
-// Detects when an element is not part of the document flow because
-// it or one of it's ancesters has display:none.
-ElementCollection.prototype.isDisplayNone = function() {
- return this.some(function(element) {
- if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
- $(element).css('display') === 'none') return true;
-
- if (element.parentNode && element.parentNode.style) {
- return $(element.parentNode).isDisplayNone();
- }
- });
-};
-
-ElementCollection.prototype.findElementWithDisplayNone = function(element) {
- return $.findElementWithDisplayNone(element);
-};
-
-
-
-OTHelpers.isElementNode = function(node) {
- return node && typeof node === 'object' && node.nodeType === 1;
-};
-
-
-// @remove
-OTHelpers.removeElement = function(element) {
- $(element).remove();
-};
-
-// @remove
-OTHelpers.removeElementById = function(elementId) {
- return $('#'+elementId).remove();
-};
-
-// @remove
-OTHelpers.removeElementsByType = function(parentElem, type) {
- return $(type, parentElem).remove();
-};
-
-// @remove
-OTHelpers.emptyElement = function(element) {
- return $(element).empty();
-};
-
-
-
-
-
-// @remove
-OTHelpers.isDisplayNone = function(element) {
- return $(element).isDisplayNone();
-};
-
-OTHelpers.findElementWithDisplayNone = function(element) {
- if ( (element.offsetWidth === 0 || element.offsetHeight === 0) &&
- $.css(element, 'display') === 'none') return element;
-
- if (element.parentNode && element.parentNode.style) {
- return $.findElementWithDisplayNone(element.parentNode);
- }
-
- return null;
-};
-
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./environment.js')
-// tb_require('./dom.js')
-
-OTHelpers.Modal = function(options) {
-
- OTHelpers.eventing(this, true);
-
- var callback = arguments[arguments.length - 1];
-
- if(!OTHelpers.isFunction(callback)) {
- throw new Error('OTHelpers.Modal2 must be given a callback');
- }
-
- if(arguments.length < 2) {
- options = {};
- }
-
- var domElement = document.createElement('iframe');
-
- domElement.id = options.id || OTHelpers.uuid();
- domElement.style.position = 'absolute';
- domElement.style.position = 'fixed';
- domElement.style.height = '100%';
- domElement.style.width = '100%';
- domElement.style.top = '0px';
- domElement.style.left = '0px';
- domElement.style.right = '0px';
- domElement.style.bottom = '0px';
- domElement.style.zIndex = 1000;
- domElement.style.border = '0';
-
- try {
- domElement.style.backgroundColor = 'rgba(0,0,0,0.2)';
- } catch (err) {
- // Old IE browsers don't support rgba and we still want to show the upgrade message
- // but we just make the background of the iframe completely transparent.
- domElement.style.backgroundColor = 'transparent';
- domElement.setAttribute('allowTransparency', 'true');
- }
-
- domElement.scrolling = 'no';
- domElement.setAttribute('scrolling', 'no');
-
- // This is necessary for IE, as it will not inherit it's doctype from
- // the parent frame.
- var frameContent = '' +
- '' +
- '' +
- '';
-
- var wrappedCallback = function() {
- var doc = domElement.contentDocument || domElement.contentWindow.document;
-
- if (OTHelpers.env.iframeNeedsLoad) {
- doc.body.style.backgroundColor = 'transparent';
- doc.body.style.border = 'none';
-
- if (OTHelpers.env.name !== 'IE') {
- // Skip this for IE as we use the bookmarklet workaround
- // for THAT browser.
- doc.open();
- doc.write(frameContent);
- doc.close();
- }
- }
-
- callback(
- domElement.contentWindow,
- doc
- );
- };
-
- document.body.appendChild(domElement);
-
- if(OTHelpers.env.iframeNeedsLoad) {
- if (OTHelpers.env.name === 'IE') {
- // This works around some issues with IE and document.write.
- // Basically this works by slightly abusing the bookmarklet/scriptlet
- // functionality that all browsers support.
- domElement.contentWindow.contents = frameContent;
- /*jshint scripturl:true*/
- domElement.src = 'javascript:window["contents"]';
- /*jshint scripturl:false*/
- }
-
- OTHelpers.on(domElement, 'load', wrappedCallback);
- } else {
- setTimeout(wrappedCallback, 0);
- }
-
- this.close = function() {
- OTHelpers.removeElement(domElement);
- this.trigger('closed');
- this.element = domElement = null;
- return this;
- };
-
- this.element = domElement;
-
-};
-
-/*
- * getComputedStyle from
- * https://github.com/jonathantneal/Polyfills-for-IE8/blob/master/getComputedStyle.js
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-
-/*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/
-
-(function() {
-
- /*jshint eqnull: true, browser: true */
-
-
- function getPixelSize(element, style, property, fontSize) {
- var sizeWithSuffix = style[property],
- size = parseFloat(sizeWithSuffix),
- suffix = sizeWithSuffix.split(/\d/)[0],
- rootSize;
-
- fontSize = fontSize != null ?
- fontSize : /%|em/.test(suffix) && element.parentElement ?
- getPixelSize(element.parentElement, element.parentElement.currentStyle, 'fontSize', null) :
- 16;
- rootSize = property === 'fontSize' ?
- fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;
-
- return (suffix === 'em') ?
- size * fontSize : (suffix === 'in') ?
- size * 96 : (suffix === 'pt') ?
- size * 96 / 72 : (suffix === '%') ?
- size / 100 * rootSize : size;
- }
-
- function setShortStyleProperty(style, property) {
- var
- borderSuffix = property === 'border' ? 'Width' : '',
- t = property + 'Top' + borderSuffix,
- r = property + 'Right' + borderSuffix,
- b = property + 'Bottom' + borderSuffix,
- l = property + 'Left' + borderSuffix;
-
- style[property] = (style[t] === style[r] === style[b] === style[l] ? [style[t]]
- : style[t] === style[b] && style[l] === style[r] ? [style[t], style[r]]
- : style[l] === style[r] ? [style[t], style[r], style[b]]
- : [style[t], style[r], style[b], style[l]]).join(' ');
- }
-
- function CSSStyleDeclaration(element) {
- var currentStyle = element.currentStyle,
- style = this,
- fontSize = getPixelSize(element, currentStyle, 'fontSize', null),
- property;
-
- for (property in currentStyle) {
- if (/width|height|margin.|padding.|border.+W/.test(property) && style[property] !== 'auto') {
- style[property] = getPixelSize(element, currentStyle, property, fontSize) + 'px';
- } else if (property === 'styleFloat') {
- /*jshint -W069 */
- style['float'] = currentStyle[property];
- } else {
- style[property] = currentStyle[property];
- }
- }
-
- setShortStyleProperty(style, 'margin');
- setShortStyleProperty(style, 'padding');
- setShortStyleProperty(style, 'border');
-
- style.fontSize = fontSize + 'px';
-
- return style;
- }
-
- CSSStyleDeclaration.prototype = {
- constructor: CSSStyleDeclaration,
- getPropertyPriority: function () {},
- getPropertyValue: function ( prop ) {
- return this[prop] || '';
- },
- item: function () {},
- removeProperty: function () {},
- setProperty: function () {},
- getPropertyCSSValue: function () {}
- };
-
- function getComputedStyle(element) {
- return new CSSStyleDeclaration(element);
- }
-
-
- OTHelpers.getComputedStyle = function(element) {
- if(element &&
- element.ownerDocument &&
- element.ownerDocument.defaultView &&
- element.ownerDocument.defaultView.getComputedStyle) {
- return element.ownerDocument.defaultView.getComputedStyle(element);
- } else {
- return getComputedStyle(element);
- }
- };
-
-})();
-
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./callbacks.js')
-// tb_require('./dom.js')
-
-var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) {
- var oldStyles = {};
-
- var getStyle = function getStyle(style) {
- switch (style) {
- case 'width':
- return $(element).width();
-
- case 'height':
- return $(element).height();
-
- default:
- return $(element).css(style);
- }
- };
-
- // get the inital values
- $.forEach(stylesToObserve, function(style) {
- oldStyles[style] = getStyle(style);
- });
-
- var observer = new MutationObserver(function(mutations) {
- var changeSet = {};
-
- $.forEach(mutations, function(mutation) {
- if (mutation.attributeName !== 'style') return;
-
- var isHidden = $.isDisplayNone(element);
-
- $.forEach(stylesToObserve, function(style) {
- if(isHidden && (style === 'width' || style === 'height')) return;
-
- var newValue = getStyle(style);
-
- if (newValue !== oldStyles[style]) {
- changeSet[style] = [oldStyles[style], newValue];
- oldStyles[style] = newValue;
- }
- });
- });
-
- if (!$.isEmpty(changeSet)) {
- // Do this after so as to help avoid infinite loops of mutations.
- $.callAsync(function() {
- onChange.call(null, changeSet);
- });
- }
- });
-
- observer.observe(element, {
- attributes:true,
- attributeFilter: ['style'],
- childList:false,
- characterData:false,
- subtree:false
- });
-
- return observer;
-};
-
-var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) {
- var observer = new MutationObserver(function(mutations) {
- var removedNodes = [];
-
- $.forEach(mutations, function(mutation) {
- if (mutation.removedNodes.length) {
- removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes));
- }
- });
-
- if (removedNodes.length) {
- // Do this after so as to help avoid infinite loops of mutations.
- $.callAsync(function() {
- onChange($(removedNodes));
- });
- }
- });
-
- observer.observe(element, {
- attributes:false,
- childList:true,
- characterData:false,
- subtree:true
- });
-
- return observer;
-};
-
-var observeSize = function (element, onChange) {
- var previousSize = {
- width: 0,
- height: 0
- };
-
- var interval = setInterval(function() {
- var rect = element.getBoundingClientRect();
- if (previousSize.width !== rect.width || previousSize.height !== rect.height) {
- onChange(rect, previousSize);
- previousSize = {
- width: rect.width,
- height: rect.height
- };
- }
- }, 1000 / 5);
-
- return {
- disconnect: function() {
- clearInterval(interval);
- }
- };
-};
-
-// Allows an +onChange+ callback to be triggered when specific style properties
-// of +element+ are notified. The callback accepts a single parameter, which is
-// a hash where the keys are the style property that changed and the values are
-// an array containing the old and new values ([oldValue, newValue]).
-//
-// Width and Height changes while the element is display: none will not be
-// fired until such time as the element becomes visible again.
-//
-// This function returns the MutationObserver itself. Once you no longer wish
-// to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// dimensionsObserver = OTHelpers(object).observeStyleChanges(,
-// ['width', 'height'], function(changeSet) {
-// OT.debug("The new width and height are " +
-// changeSet.width[1] + ',' + changeSet.height[1]);
-// });
-//
-// Cleaning up
-// // stop observing changes
-// dimensionsObserver.disconnect();
-// dimensionsObserver = null;
-//
-ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeStyleChanges(element, stylesToObserve, onChange)
- );
- });
-
- return observers;
-};
-
-// trigger the +onChange+ callback whenever
-// 1. +element+ is removed
-// 2. or an immediate child of +element+ is removed.
-//
-// This function returns the MutationObserver itself. Once you no longer wish
-// to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) {
-// OT.debug("Some child nodes were removed");
-// removedNodes.forEach(function(node) {
-// OT.debug(node);
-// });
-// });
-//
-// Cleaning up
-// // stop observing changes
-// nodeObserver.disconnect();
-// nodeObserver = null;
-//
-ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeNodeOrChildNodeRemoval(element, onChange)
- );
- });
-
- return observers;
-};
-
-// trigger the +onChange+ callback whenever the width or the height of the element changes
-//
-// Once you no longer wish to observe the element you should call disconnect on the observer.
-//
-// Observing changes:
-// // observe changings to the width and height of object
-// sizeObserver = OTHelpers(object).observeSize(function(newSize, previousSize) {
-// OT.debug("The new width and height are " +
-// newSize.width + ',' + newSize.height);
-// });
-//
-// Cleaning up
-// // stop observing changes
-// sizeObserver.disconnect();
-// sizeObserver = null;
-//
-ElementCollection.prototype.observeSize = function(onChange) {
- var observers = [];
-
- this.forEach(function(element) {
- observers.push(
- observeSize(element, onChange)
- );
- });
-
- return observers;
-};
-
-
-// @remove
-OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) {
- return $(element).observeStyleChanges(stylesToObserve, onChange)[0];
-};
-
-// @remove
-OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) {
- return $(element).observeNodeOrChildNodeRemoval(onChange)[0];
-};
-
-/*jshint browser:true, smarttabs:true */
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-// tb_require('./capabilities.js')
-
-// Returns true if the client supports element.classList
-OTHelpers.registerCapability('classList', function() {
- return (typeof document !== 'undefined') && ('classList' in document.createElement('a'));
-});
-
-
-function hasClass (element, className) {
- if (!className) return false;
-
- if ($.hasCapabilities('classList')) {
- return element.classList.contains(className);
- }
-
- return element.className.indexOf(className) > -1;
-}
-
-function toggleClasses (element, classNames) {
- if (!classNames || classNames.length === 0) return;
-
- // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc
- if (element.nodeType !== 1) {
- return;
- }
-
- var numClasses = classNames.length,
- i = 0;
-
- if ($.hasCapabilities('classList')) {
- for (; i 0) {
- return element.offsetWidth + 'px';
- }
-
- return $(element).css('width');
- },
-
- _height = function(element) {
- if (element.offsetHeight > 0) {
- return element.offsetHeight + 'px';
- }
-
- return $(element).css('height');
- };
-
- ElementCollection.prototype.width = function (newWidth) {
- if (newWidth) {
- this.css('width', newWidth);
- return this;
- }
- else {
- if (this.isDisplayNone()) {
- return this.makeVisibleAndYield(function(element) {
- return _width(element);
- })[0];
- }
- else {
- return _width(this.get(0));
- }
- }
- };
-
- ElementCollection.prototype.height = function (newHeight) {
- if (newHeight) {
- this.css('height', newHeight);
- return this;
- }
- else {
- if (this.isDisplayNone()) {
- // We can't get the height, probably since the element is hidden.
- return this.makeVisibleAndYield(function(element) {
- return _height(element);
- })[0];
- }
- else {
- return _height(this.get(0));
- }
- }
- };
-
- // @remove
- OTHelpers.width = function(element, newWidth) {
- var ret = $(element).width(newWidth);
- return newWidth ? OTHelpers : ret;
- };
-
- // @remove
- OTHelpers.height = function(element, newHeight) {
- var ret = $(element).height(newHeight);
- return newHeight ? OTHelpers : ret;
- };
-
-})();
-
-
-// CSS helpers helpers
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./dom.js')
-// tb_require('./getcomputedstyle.js')
-
-(function() {
-
- var displayStateCache = {},
- defaultDisplays = {};
-
- var defaultDisplayValueForElement = function (element) {
- if (defaultDisplays[element.ownerDocument] &&
- defaultDisplays[element.ownerDocument][element.nodeName]) {
- return defaultDisplays[element.ownerDocument][element.nodeName];
- }
-
- if (!defaultDisplays[element.ownerDocument]) defaultDisplays[element.ownerDocument] = {};
-
- // We need to know what display value to use for this node. The easiest way
- // is to actually create a node and read it out.
- var testNode = element.ownerDocument.createElement(element.nodeName),
- defaultDisplay;
-
- element.ownerDocument.body.appendChild(testNode);
- defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] =
- $(testNode).css('display');
-
- $(testNode).remove();
- testNode = null;
-
- return defaultDisplay;
- };
-
- var isHidden = function (element) {
- var computedStyle = $.getComputedStyle(element);
- return computedStyle.getPropertyValue('display') === 'none';
- };
-
- var setCssProperties = function (element, hash) {
- var style = element.style;
-
- for (var cssName in hash) {
- if (hash.hasOwnProperty(cssName)) {
- style[cssName] = hash[cssName];
- }
- }
- };
-
- var setCssProperty = function (element, name, value) {
- element.style[name] = value;
- };
-
- var getCssProperty = function (element, unnormalisedName) {
- // Normalise vendor prefixes from the form MozTranform to -moz-transform
- // except for ms extensions, which are weird...
-
- var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(),
- computedStyle = $.getComputedStyle(element),
- currentValue = computedStyle.getPropertyValue(name);
-
- if (currentValue === '') {
- currentValue = element.style[name];
- }
-
- return currentValue;
- };
-
- var applyCSS = function(element, styles, callback) {
- var oldStyles = {},
- name,
- ret;
-
- // Backup the old styles
- for (name in styles) {
- if (styles.hasOwnProperty(name)) {
- // We intentionally read out of style here, instead of using the css
- // helper. This is because the css helper uses querySelector and we
- // only want to pull values out of the style (domeElement.style) hash.
- oldStyles[name] = element.style[name];
-
- $(element).css(name, styles[name]);
- }
- }
-
- ret = callback(element);
-
- // Restore the old styles
- for (name in styles) {
- if (styles.hasOwnProperty(name)) {
- $(element).css(name, oldStyles[name] || '');
- }
- }
-
- return ret;
- };
-
- ElementCollection.prototype.show = function() {
- return this.forEach(function(element) {
- var display = element.style.display;
-
- if (display === '' || display === 'none') {
- element.style.display = displayStateCache[element] || '';
- delete displayStateCache[element];
- }
-
- if (isHidden(element)) {
- // It's still hidden so there's probably a stylesheet that declares this
- // element as display:none;
- displayStateCache[element] = 'none';
-
- element.style.display = defaultDisplayValueForElement(element);
- }
- });
- };
-
- ElementCollection.prototype.hide = function() {
- return this.forEach(function(element) {
- if (element.style.display === 'none') return;
-
- displayStateCache[element] = element.style.display;
- element.style.display = 'none';
- });
- };
-
- ElementCollection.prototype.css = function(nameOrHash, value) {
- if (this.length === 0) return;
-
- if (typeof(nameOrHash) !== 'string') {
-
- return this.forEach(function(element) {
- setCssProperties(element, nameOrHash);
- });
-
- } else if (value !== undefined) {
-
- return this.forEach(function(element) {
- setCssProperty(element, nameOrHash, value);
- });
-
- } else {
- return getCssProperty(this.first, nameOrHash, value);
- }
- };
-
- // Apply +styles+ to +element+ while executing +callback+, restoring the previous
- // styles after the callback executes.
- ElementCollection.prototype.applyCSS = function (styles, callback) {
- var results = [];
-
- this.forEach(function(element) {
- results.push(applyCSS(element, styles, callback));
- });
-
- return results;
- };
-
-
- // Make +element+ visible while executing +callback+.
- ElementCollection.prototype.makeVisibleAndYield = function (callback) {
- var hiddenVisually = {
- display: 'block',
- visibility: 'hidden'
- },
- results = [];
-
- this.forEach(function(element) {
- // find whether it's the element or an ancestor that's display none and
- // then apply to whichever it is
- var targetElement = $.findElementWithDisplayNone(element);
- if (!targetElement) {
- results.push(void 0);
- }
- else {
- results.push(
- applyCSS(targetElement, hiddenVisually, callback)
- );
- }
- });
-
- return results;
- };
-
-
- // @remove
- OTHelpers.show = function(element) {
- return $(element).show();
- };
-
- // @remove
- OTHelpers.hide = function(element) {
- return $(element).hide();
- };
-
- // @remove
- OTHelpers.css = function(element, nameOrHash, value) {
- return $(element).css(nameOrHash, value);
- };
-
- // @remove
- OTHelpers.applyCSS = function(element, styles, callback) {
- return $(element).applyCSS(styles, callback);
- };
-
- // @remove
- OTHelpers.makeVisibleAndYield = function(element, callback) {
- return $(element).makeVisibleAndYield(callback);
- };
-
-})();
-
-// tb_require('../helpers.js')
-
-/**@licence
- * Copyright (c) 2010 Caolan McMahon
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- **/
-
-
-(function() {
-
- OTHelpers.setImmediate = (function() {
- if (typeof process === 'undefined' || !(process.nextTick)) {
- if (typeof setImmediate === 'function') {
- return function (fn) {
- // not a direct alias for IE10 compatibility
- setImmediate(fn);
- };
- }
- return function (fn) {
- setTimeout(fn, 0);
- };
- }
- if (typeof setImmediate !== 'undefined') {
- return setImmediate;
- }
- return process.nextTick;
- })();
-
- OTHelpers.iterator = function(tasks) {
- var makeCallback = function (index) {
- var fn = function () {
- if (tasks.length) {
- tasks[index].apply(null, arguments);
- }
- return fn.next();
- };
- fn.next = function () {
- return (index < tasks.length - 1) ? makeCallback(index + 1) : null;
- };
- return fn;
- };
- return makeCallback(0);
- };
-
- OTHelpers.waterfall = function(array, done) {
- done = done || function () {};
- if (array.constructor !== Array) {
- return done(new Error('First argument to waterfall must be an array of functions'));
- }
-
- if (!array.length) {
- return done();
- }
-
- var next = function(iterator) {
- return function (err) {
- if (err) {
- done.apply(null, arguments);
- done = function () {};
- } else {
- var args = prototypeSlice.call(arguments, 1),
- nextFn = iterator.next();
- if (nextFn) {
- args.push(next(nextFn));
- } else {
- args.push(done);
- }
- OTHelpers.setImmediate(function() {
- iterator.apply(null, args);
- });
- }
- };
- };
-
- next(OTHelpers.iterator(array))();
- };
-
-})();
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-(function() {
-
- var requestAnimationFrame = window.requestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.msRequestAnimationFrame;
-
- if (requestAnimationFrame) {
- requestAnimationFrame = OTHelpers.bind(requestAnimationFrame, window);
- }
- else {
- var lastTime = 0;
- var startTime = OTHelpers.now();
-
- requestAnimationFrame = function(callback){
- var currTime = OTHelpers.now();
- var timeToCall = Math.max(0, 16 - (currTime - lastTime));
- var id = window.setTimeout(function() { callback(currTime - startTime); }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- }
-
- OTHelpers.requestAnimationFrame = requestAnimationFrame;
-})();
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-
-(function() {
-
- // Singleton interval
- var logQueue = [],
- queueRunning = false;
-
- OTHelpers.Analytics = function(loggingUrl, debugFn) {
-
- var endPoint = loggingUrl + '/logging/ClientEvent',
- endPointQos = loggingUrl + '/logging/ClientQos',
-
- reportedErrors = {},
-
- send = function(data, isQos, callback) {
- OTHelpers.post((isQos ? endPointQos : endPoint) + '?_=' + OTHelpers.uuid.v4(), {
- body: data,
- xdomainrequest: ($.env.name === 'IE' && $.env.version < 10),
- headers: {
- 'Content-Type': 'application/json'
- }
- }, callback);
- },
-
- throttledPost = function() {
- // Throttle logs so that they only happen 1 at a time
- if (!queueRunning && logQueue.length > 0) {
- queueRunning = true;
- var curr = logQueue[0];
-
- // Remove the current item and send the next log
- var processNextItem = function() {
- logQueue.shift();
- queueRunning = false;
- throttledPost();
- };
-
- if (curr) {
- send(curr.data, curr.isQos, function(err) {
- if (err) {
- var debugMsg = 'Failed to send ClientEvent, moving on to the next item.';
- if (debugFn) {
- debugFn(debugMsg);
- } else {
- console.log(debugMsg);
- }
- // There was an error, move onto the next item
- } else {
- curr.onComplete();
- }
- setTimeout(processNextItem, 50);
- });
- }
- }
- },
-
- post = function(data, onComplete, isQos) {
- logQueue.push({
- data: data,
- onComplete: onComplete,
- isQos: isQos
- });
-
- throttledPost();
- },
-
- shouldThrottleError = function(code, type, partnerId) {
- if (!partnerId) return false;
-
- var errKey = [partnerId, type, code].join('_'),
- //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId);
- msgLimit = 100;
- if (msgLimit === null || msgLimit === undefined) return false;
- return (reportedErrors[errKey] || 0) <= msgLimit;
- };
-
- // Log an error via ClientEvents.
- //
- // @param [String] code
- // @param [String] type
- // @param [String] message
- // @param [Hash] details additional error details
- //
- // @param [Hash] options the options to log the client event with.
- // @option options [String] action The name of the Event that we are logging. E.g.
- // 'TokShowLoaded'. Required.
- // @option options [String] variation Usually used for Split A/B testing, when you
- // have multiple variations of the +_action+.
- // @option options [String] payload The payload. Required.
- // @option options [String] sessionId The active OpenTok session, if there is one
- // @option options [String] connectionId The active OpenTok connectionId, if there is one
- // @option options [String] partnerId
- // @option options [String] guid ...
- // @option options [String] streamId ...
- // @option options [String] section ...
- // @option options [String] clientVersion ...
- //
- // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner
- // from the dynamic config for X) of each error type for each partner. Reports can be
- // disabled/enabled globally or on a per partner basis (per partner settings
- // take precedence) using exceptionLogging.enabled.
- //
- this.logError = function(code, type, message, details, options) {
- if (!options) options = {};
- var partnerId = options.partnerId;
-
- if (shouldThrottleError(code, type, partnerId)) {
- //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' +
- // code + ' for partner ' + (partnerId || 'No Partner Id'));
- return;
- }
-
- var errKey = [partnerId, type, code].join('_'),
- payload = details ? details : null;
-
- reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ?
- reportedErrors[errKey] + 1 : 1;
- this.logEvent(OTHelpers.extend(options, {
- action: type + '.' + code,
- payload: payload
- }), false);
- };
-
- // Log a client event to the analytics backend.
- //
- // @example Logs a client event called 'foo'
- // this.logEvent({
- // action: 'foo',
- // payload: 'bar',
- // sessionId: sessionId,
- // connectionId: connectionId
- // }, false)
- //
- // @param [Hash] data the data to log the client event with.
- // @param [Boolean] qos Whether this is a QoS event.
- //
- this.logEvent = function(data, qos, throttle) {
- if (!qos) qos = false;
-
- if (throttle && !isNaN(throttle)) {
- if (Math.random() > throttle) {
- return;
- }
- }
-
- // remove properties that have null values:
- for (var key in data) {
- if (data.hasOwnProperty(key) && data[key] === null) {
- delete data[key];
- }
- }
-
- // TODO: catch error when stringifying an object that has a circular reference
- data = JSON.stringify(data);
-
- var onComplete = function() {
- // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation']
- // + ', payload: ' + data['payload'] + '}');
- };
-
- post(data, onComplete, qos);
- };
-
- // Log a client QOS to the analytics backend.
- // Log a client QOS to the analytics backend.
- // @option options [String] action The name of the Event that we are logging.
- // E.g. 'TokShowLoaded'. Required.
- // @option options [String] variation Usually used for Split A/B testing, when
- // you have multiple variations of the +_action+.
- // @option options [String] payload The payload. Required.
- // @option options [String] sessionId The active OpenTok session, if there is one
- // @option options [String] connectionId The active OpenTok connectionId, if there is one
- // @option options [String] partnerId
- // @option options [String] guid ...
- // @option options [String] streamId ...
- // @option options [String] section ...
- // @option options [String] clientVersion ...
- //
- this.logQOS = function(options) {
- this.logEvent(options, true);
- };
- };
-
-})();
-
-// AJAX helpers
-
-/*jshint browser:true, smarttabs:true*/
-
-// tb_require('../helpers.js')
-// tb_require('./ajax/node.js')
-// tb_require('./ajax/browser.js')
-
-OTHelpers.get = function(url, options, callback) {
- var _options = OTHelpers.extend(options || {}, {
- method: 'GET'
- });
- OTHelpers.request(url, _options, callback);
-};
-
-
-OTHelpers.post = function(url, options, callback) {
- var _options = OTHelpers.extend(options || {}, {
- method: 'POST'
- });
-
- if(_options.xdomainrequest) {
- OTHelpers.xdomainRequest(url, _options, callback);
- } else {
- OTHelpers.request(url, _options, callback);
- }
-};
-
})(window, window.OTHelpers);
/**
- * @license TB Plugin 0.4.0.10 6935b20 HEAD
+ * @license TB Plugin 0.4.0.12 6e40a4e v0.4.0.12-branch
* http://www.tokbox.com/
*
* Copyright (c) 2015 TokBox, Inc.
*
- * Date: July 13 05:38:06 2015
+ * Date: October 28 03:45:04 2015
*
*/
@@ -6126,24 +6161,25 @@ if (scope.OTPlugin !== void 0) return;
var $ = OTHelpers;
+// Magic number to avoid plugin crashes through a settimeout call
+window.EmpiricDelay = 3000;
+
// TB must exist first, otherwise we can't do anything
// if (scope.OT === void 0) return;
// Establish the environment that we're running in
// Note: we don't currently support 64bit IE
-var isSupported = $.env.name === 'Safari' ||
- ($.env.name === 'IE' && $.env.version >= 8 &&
+var isSupported = ($.env.name === 'IE' && $.env.version >= 8 &&
$.env.userAgent.indexOf('x64') === -1),
pluginIsReady = false;
-
var OTPlugin = {
- isSupported: function () { return isSupported; },
+ isSupported: function() { return isSupported; },
isReady: function() { return pluginIsReady; },
meta: {
- mimeType: 'application/x-opentokie,version=0.4.0.10',
- activeXName: 'TokBox.OpenTokIE.0.4.0.10',
- version: '0.4.0.10'
+ mimeType: 'application/x-opentokplugin,version=0.4.0.12',
+ activeXName: 'TokBox.OpenTokPlugin.0.4.0.12',
+ version: '0.4.0.12'
},
useLoggingFrom: function(host) {
@@ -6156,11 +6192,9 @@ var OTPlugin = {
}
};
-
// Add logging methods
$.useLogHelpers(OTPlugin);
-
scope.OTPlugin = OTPlugin;
$.registerCapability('otplugin', function() {
@@ -6170,7 +6204,7 @@ $.registerCapability('otplugin', function() {
// If this client isn't supported we still make sure that OTPlugin is defined
// and the basic API (isSupported() and isInstalled()) is created.
if (!OTPlugin.isSupported()) {
- OTPlugin.isInstalled = function isInstalled () { return false; };
+ OTPlugin.isInstalled = function isInstalled() { return false; };
return;
}
@@ -6180,9 +6214,9 @@ if (!OTPlugin.isSupported()) {
// Shims for various missing things from JS
// Applied only after init is called to prevent unnecessary polution
-var shim = function shim () {
+var shim = function shim() {
if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
+ Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
@@ -6190,8 +6224,8 @@ var shim = function shim () {
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
- FNOP = function () {},
- fBound = function () {
+ FNOP = function() {},
+ fBound = function() {
return fToBind.apply(this instanceof FNOP && oThis ?
this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
@@ -6203,14 +6237,14 @@ var shim = function shim () {
};
}
- if(!Array.isArray) {
- Array.isArray = function (vArg) {
+ if (!Array.isArray) {
+ Array.isArray = function(vArg) {
return Object.prototype.toString.call(vArg) === '[object Array]';
};
}
if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (searchElement, fromIndex) {
+ Array.prototype.indexOf = function(searchElement, fromIndex) {
var i,
pivot = (fromIndex) ? fromIndex : 0,
length;
@@ -6270,33 +6304,59 @@ var shim = function shim () {
};
}
};
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+
+/* exported TaskQueue */
+
+var TaskQueue = function() {
+ var Proto = function TaskQueue() {},
+ api = new Proto(),
+ tasks = [];
+
+ api.add = function(fn, context) {
+ if (context) {
+ tasks.push($.bind(fn, context));
+ } else {
+ tasks.push(fn);
+ }
+ };
+
+ api.runAll = function() {
+ var task;
+ while ((task = tasks.shift())) {
+ task();
+ }
+ };
+
+ return api;
+};
+
// tb_require('./header.js')
// tb_require('./shims.js')
/* global curryCallAsync:true */
/* exported RumorSocket */
-var RumorSocket = function (plugin, server) {
- var Proto = function RumorSocket () {},
+var RumorSocket = function(plugin, server) {
+ var Proto = function RumorSocket() {},
api = new Proto(),
connected = false,
rumorID,
_onOpen,
_onClose;
-
try {
rumorID = plugin._.RumorInit(server, '');
- }
- catch(e) {
+ } catch (e) {
OTPlugin.error('Error creating the Rumor Socket: ', e.message);
}
- if(!rumorID) {
+ if (!rumorID) {
throw new Error('Could not initialise OTPlugin rumor connection');
}
-
api.open = function() {
connected = true;
plugin._.RumorOpen(rumorID);
@@ -6359,15 +6419,15 @@ var RumorSocket = function (plugin, server) {
/* exported refCountBehaviour */
-var refCountBehaviour = function refCountBehaviour (api) {
+var refCountBehaviour = function refCountBehaviour(api) {
var _liveObjects = [];
- api.addRef = function (ref) {
+ api.addRef = function(ref) {
_liveObjects.push(ref);
return api;
};
- api.removeRef = function (ref) {
+ api.removeRef = function(ref) {
if (_liveObjects.length === 0) return;
var index = _liveObjects.indexOf(ref);
@@ -6382,7 +6442,7 @@ var refCountBehaviour = function refCountBehaviour (api) {
return api;
};
- api.removeAllRefs = function () {
+ api.removeAllRefs = function() {
while (_liveObjects.length) {
_liveObjects.shift().destroy();
}
@@ -6391,19 +6451,28 @@ var refCountBehaviour = function refCountBehaviour (api) {
// tb_require('./header.js')
// tb_require('./shims.js')
+// tb_require('./task_queue.js')
-/* global curryCallAsync:true */
+/* global curryCallAsync:true, TaskQueue:true */
/* exported pluginEventingBehaviour */
-var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
- var eventHandlers = {};
+var pluginEventingBehaviour = function pluginEventingBehaviour(api) {
+ var eventHandlers = {},
+ pendingTasks = new TaskQueue(),
+ isReady = false;
var onCustomEvent = function() {
var args = Array.prototype.slice.call(arguments);
api.emit(args.shift(), args);
};
- api.on = function (name, callback, context) {
+ var devicesChanged = function() {
+ var args = Array.prototype.slice.call(arguments);
+ OTPlugin.debug(args);
+ api.emit('devicesChanged', args);
+ };
+
+ api.on = function(name, callback, context) {
if (!eventHandlers.hasOwnProperty(name)) {
eventHandlers[name] = [];
}
@@ -6412,7 +6481,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
return api;
};
- api.off = function (name, callback, context) {
+ api.off = function(name, callback, context) {
if (!eventHandlers.hasOwnProperty(name) ||
eventHandlers[name].length === 0) {
return;
@@ -6426,8 +6495,8 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
return api;
};
- api.once = function (name, callback, context) {
- var fn = function () {
+ api.once = function(name, callback, context) {
+ var fn = function() {
api.off(name, fn);
return callback.apply(context, arguments);
};
@@ -6436,7 +6505,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
return api;
};
- api.emit = function (name, args) {
+ api.emit = function(name, args) {
$.callAsync(function() {
if (!eventHandlers.hasOwnProperty(name) && eventHandlers[name].length) {
return;
@@ -6450,7 +6519,20 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
return api;
};
- var onReady = function onReady (readyCallback) {
+ // Calling this will bind a listener to the devicesChanged events that
+ // the plugin emits and then rebroadcast them.
+ api.listenForDeviceChanges = function() {
+ if (!isReady) {
+ pendingTasks.add(api.listenForDeviceChanges, api);
+ return;
+ }
+
+ setTimeout(function() {
+ api._.registerXCallback('devicesChanged', devicesChanged);
+ }, window.EmpiricDelay);
+ };
+
+ var onReady = function onReady(readyCallback) {
if (api._.on) {
// If the plugin supports custom events we'll use them
api._.on(-1, {
@@ -6458,17 +6540,25 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
});
}
+ var internalReadyCallback = function() {
+ // It's not safe to do most plugin operations until the plugin
+ // is ready for us to do so. We use isReady as a guard in
+ isReady = true;
+
+ pendingTasks.runAll();
+ readyCallback.call(api);
+ };
+
// Only the main plugin has an initialise method
if (api._.initialise) {
- api.on('ready', curryCallAsync(readyCallback));
+ api.on('ready', curryCallAsync(internalReadyCallback));
api._.initialise();
- }
- else {
- readyCallback.call(api);
+ } else {
+ internalReadyCallback.call(api);
}
};
- return function (completion) {
+ return function(completion) {
onReady(function(err) {
if (err) {
OTPlugin.error('Error while starting up plugin ' + api.uuid + ': ' + err);
@@ -6481,6 +6571,7 @@ var pluginEventingBehaviour = function pluginEventingBehaviour (api) {
});
};
};
+
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./ref_count_behaviour.js')
@@ -6493,7 +6584,7 @@ var PROXY_LOAD_TIMEOUT = 5000;
var objectTimeouts = {};
-var curryCallAsync = function curryCallAsync (fn) {
+var curryCallAsync = function curryCallAsync(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(fn);
@@ -6501,7 +6592,7 @@ var curryCallAsync = function curryCallAsync (fn) {
};
};
-var clearGlobalCallback = function clearGlobalCallback (callbackId) {
+var clearGlobalCallback = function clearGlobalCallback(callbackId) {
if (!callbackId) return;
if (objectTimeouts[callbackId]) {
@@ -6518,7 +6609,7 @@ var clearGlobalCallback = function clearGlobalCallback (callbackId) {
}
};
-var waitOnGlobalCallback = function waitOnGlobalCallback (callbackId, completion) {
+var waitOnGlobalCallback = function waitOnGlobalCallback(callbackId, completion) {
objectTimeouts[callbackId] = setTimeout(function() {
clearGlobalCallback(callbackId);
completion('The object timed out while loading.');
@@ -6533,11 +6624,11 @@ var waitOnGlobalCallback = function waitOnGlobalCallback (callbackId, completion
};
};
-var generateCallbackID = function generateCallbackID () {
+var generateCallbackID = function generateCallbackID() {
return 'OTPlugin_loaded_' + $.uuid().replace(/\-+/g, '');
};
-var generateObjectHtml = function generateObjectHtml (callbackId, options) {
+var generateObjectHtml = function generateObjectHtml(callbackId, options) {
options = options || {};
var objBits = [],
@@ -6553,7 +6644,6 @@ var generateObjectHtml = function generateObjectHtml (callbackId, options) {
onload: callbackId
};
-
if (options.isVisible !== true) {
attrs.push('visibility="hidden"');
}
@@ -6570,9 +6660,7 @@ var generateObjectHtml = function generateObjectHtml (callbackId, options) {
return objBits.join('');
};
-
-
-var createObject = function createObject (callbackId, options, completion) {
+var createObject = function createObject(callbackId, options, completion) {
options = options || {};
var html = generateObjectHtml(callbackId, options),
@@ -6587,9 +6675,8 @@ var createObject = function createObject (callbackId, options, completion) {
// }
// else {
-
doc.body.insertAdjacentHTML('beforeend', html);
- var object = doc.body.querySelector('#'+callbackId+'_obj');
+ var object = doc.body.querySelector('#' + callbackId + '_obj');
// object.setAttribute('type', options.mimeType);
@@ -6598,7 +6685,7 @@ var createObject = function createObject (callbackId, options, completion) {
};
// Reference counted wrapper for a plugin object
-var createPluginProxy = function (options, completion) {
+var createPluginProxy = function(options, completion) {
var Proto = function PluginProxy() {},
api = new Proto(),
waitForReadySignal = pluginEventingBehaviour(api);
@@ -6608,20 +6695,18 @@ var createPluginProxy = function (options, completion) {
// Assign +plugin+ to this object and setup all the public
// accessors that relate to the DOM Object.
//
- var setPlugin = function setPlugin (plugin) {
+ var setPlugin = function setPlugin(plugin) {
if (plugin) {
api._ = plugin;
api.parentElement = plugin.parentElement;
api.$ = $(plugin);
- }
- else {
+ } else {
api._ = null;
api.parentElement = null;
api.$ = $();
}
};
-
api.uuid = generateCallbackID();
api.isValid = function() {
@@ -6637,11 +6722,12 @@ var createPluginProxy = function (options, completion) {
api.emit('destroy');
};
-
+ api.enumerateDevices = function(completion) {
+ api._.enumerateDevices(completion);
+ };
/// Initialise
-
// The next statement creates the raw plugin object accessor on the Proxy.
// This is null until we actually have created the Object.
setPlugin(null);
@@ -6678,22 +6764,18 @@ var createPluginProxy = function (options, completion) {
return api;
};
-
-
-
// Specialisation for the MediaCapturer API surface
-var makeMediaCapturerProxy = function makeMediaCapturerProxy (api) {
-
+var makeMediaCapturerProxy = function makeMediaCapturerProxy(api) {
api.selectSources = function() {
return api._.selectSources.apply(api._, arguments);
};
+ api.listenForDeviceChanges();
return api;
};
-
// Specialisation for the MediaPeer API surface
-var makeMediaPeerProxy = function makeMediaPeerProxy (api) {
+var makeMediaPeerProxy = function makeMediaPeerProxy(api) {
api.setStream = function(stream, completion) {
api._.setStream(stream);
@@ -6710,15 +6792,13 @@ var makeMediaPeerProxy = function makeMediaPeerProxy (api) {
if (api._.videoWidth > 0) {
// This fires a little too soon.
setTimeout(completion, 200);
- }
- else {
+ } else {
setTimeout(verifyStream, 500);
}
};
setTimeout(verifyStream, 500);
- }
- else {
+ } else {
// TODO Investigate whether there is a good way to detect
// when the audio is ready. Does it even matter?
@@ -6733,15 +6813,14 @@ var makeMediaPeerProxy = function makeMediaPeerProxy (api) {
return api;
};
-
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
/* exported VideoContainer */
-var VideoContainer = function (plugin, stream) {
- var Proto = function VideoContainer () {},
+var VideoContainer = function(plugin, stream) {
+ var Proto = function VideoContainer() {},
api = new Proto();
api.domElement = plugin._;
@@ -6750,7 +6829,7 @@ var VideoContainer = function (plugin, stream) {
plugin.addRef(api);
- api.appendTo = function (parentDomElement) {
+ api.appendTo = function(parentDomElement) {
if (parentDomElement && plugin._.parentNode !== parentDomElement) {
OTPlugin.debug('VideoContainer appendTo', parentDomElement);
parentDomElement.appendChild(plugin._);
@@ -6758,7 +6837,7 @@ var VideoContainer = function (plugin, stream) {
}
};
- api.show = function (completion) {
+ api.show = function(completion) {
OTPlugin.debug('VideoContainer show');
plugin._.removeAttribute('width');
plugin._.removeAttribute('height');
@@ -6773,7 +6852,7 @@ var VideoContainer = function (plugin, stream) {
return api;
};
- api.width = function (newWidth) {
+ api.width = function(newWidth) {
if (newWidth !== void 0) {
OTPlugin.debug('VideoContainer set width to ' + newWidth);
plugin._.setAttribute('width', newWidth);
@@ -6782,7 +6861,7 @@ var VideoContainer = function (plugin, stream) {
return plugin._.getAttribute('width');
};
- api.height = function (newHeight) {
+ api.height = function(newHeight) {
if (newHeight !== void 0) {
OTPlugin.debug('VideoContainer set height to ' + newHeight);
plugin._.setAttribute('height', newHeight);
@@ -6791,31 +6870,30 @@ var VideoContainer = function (plugin, stream) {
return plugin._.getAttribute('height');
};
- api.volume = function (newVolume) {
+ api.volume = function(newVolume) {
if (newVolume !== void 0) {
// TODO
OTPlugin.debug('VideoContainer setVolume not implemented: called with ' + newVolume);
- }
- else {
+ } else {
OTPlugin.debug('VideoContainer getVolume not implemented');
}
return 0.5;
};
- api.getImgData = function () {
+ api.getImgData = function() {
return plugin._.getImgData('image/png');
};
- api.videoWidth = function () {
+ api.videoWidth = function() {
return plugin._.videoWidth;
};
- api.videoHeight = function () {
+ api.videoHeight = function() {
return plugin._.videoHeight;
};
- api.destroy = function () {
+ api.destroy = function() {
plugin._.setStream(null);
plugin.removeRef(api);
};
@@ -6829,7 +6907,7 @@ var VideoContainer = function (plugin, stream) {
/* exported RTCStatsReport */
-var RTCStatsReport = function RTCStatsReport (reports) {
+var RTCStatsReport = function RTCStatsReport(reports) {
for (var id in reports) {
if (reports.hasOwnProperty(id)) {
this[id] = reports[id];
@@ -6837,7 +6915,7 @@ var RTCStatsReport = function RTCStatsReport (reports) {
}
};
-RTCStatsReport.prototype.forEach = function (callback, context) {
+RTCStatsReport.prototype.forEach = function(callback, context) {
for (var id in this) {
if (this.hasOwnProperty(id)) {
callback.call(context, this[id]);
@@ -6852,21 +6930,18 @@ RTCStatsReport.prototype.forEach = function (callback, context) {
/* global createPluginProxy:true, makeMediaPeerProxy:true, makeMediaCapturerProxy:true */
/* exported PluginProxies */
-
var PluginProxies = (function() {
- var Proto = function PluginProxies () {},
+ var Proto = function PluginProxies() {},
api = new Proto(),
proxies = {};
-
/// Private API
// This is called whenever a Proxy's destroy event fires.
- var cleanupProxyOnDestroy = function cleanupProxyOnDestroy (object) {
+ var cleanupProxyOnDestroy = function cleanupProxyOnDestroy(object) {
if (api.mediaCapturer && api.mediaCapturer.id === object.id) {
api.mediaCapturer = null;
- }
- else if (proxies.hasOwnProperty(object.id)) {
+ } else if (proxies.hasOwnProperty(object.id)) {
delete proxies[object.id];
}
@@ -6875,14 +6950,12 @@ var PluginProxies = (function() {
}
};
-
/// Public API
-
// Public accessor for the MediaCapturer
api.mediaCapturer = null;
- api.removeAll = function removeAll () {
+ api.removeAll = function removeAll() {
for (var id in proxies) {
if (proxies.hasOwnProperty(id)) {
proxies[id].destroy();
@@ -6892,7 +6965,7 @@ var PluginProxies = (function() {
if (api.mediaCapturer) api.mediaCapturer.destroy();
};
- api.create = function create (options, completion) {
+ api.create = function create(options, completion) {
var proxy = createPluginProxy(options, completion);
proxies[proxy.uuid] = proxy;
@@ -6905,7 +6978,7 @@ var PluginProxies = (function() {
return proxy;
};
- api.createMediaPeer = function createMediaPeer (options, completion) {
+ api.createMediaPeer = function createMediaPeer(options, completion) {
if ($.isFunction(options)) {
completion = options;
options = {};
@@ -6928,7 +7001,7 @@ var PluginProxies = (function() {
makeMediaPeerProxy(mediaPeer);
};
- api.createMediaCapturer = function createMediaCapturer (completion) {
+ api.createMediaCapturer = function createMediaCapturer(completion) {
if (api.mediaCapturer) {
completion.call(OTPlugin, void 0, api.mediaCapturer);
return api;
@@ -6959,8 +7032,8 @@ var PluginProxies = (function() {
// Our RTCPeerConnection shim, it should look like a normal PeerConection
// from the outside, but it actually delegates to our plugin.
//
-var PeerConnection = function (iceServers, options, plugin, ready) {
- var Proto = function PeerConnection () {},
+var PeerConnection = function(iceServers, options, plugin, ready) {
+ var Proto = function PeerConnection() {},
api = new Proto(),
id = $.uuid(),
hasLocalDescription = false,
@@ -6980,21 +7053,20 @@ var PeerConnection = function (iceServers, options, plugin, ready) {
iceconnectionstatechange: []
};
- var onAddIceCandidate = function onAddIceCandidate () {/* success */},
+ var onAddIceCandidate = function onAddIceCandidate() {/* success */},
- onAddIceCandidateFailed = function onAddIceCandidateFailed (err) {
+ onAddIceCandidateFailed = function onAddIceCandidateFailed(err) {
OTPlugin.error('Failed to process candidate');
OTPlugin.error(err);
},
- processPendingCandidates = function processPendingCandidates () {
- for (var i=0; i version2;
- }
-
- // The versions have multiple components (i.e. 0.10.30) and
- // must be compared piecewise.
- // Note: I'm ignoring the case where one version has multiple
- // components and the other doesn't.
- var v1 = version1.split('.'),
- v2 = version2.split('.'),
- versionLength = (v1.length > v2.length ? v2 : v1).length;
-
-
- for (var i = 0; i < versionLength; ++i) {
- if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
- return true;
- }
- }
-
- // Special case, v1 has extra components but the initial components
- // were identical, we assume this means newer but it might also mean
- // that someone changed versioning systems.
- if (i < v1.length) {
- return true;
- }
-
- return false;
- };
-
-
- // Work out the full mimeType (including the currently installed version)
- // of the installer.
- var findMimeTypeAndVersion = function findMimeTypeAndVersion () {
-
- if (updaterMimeType !== void 0) {
- return updaterMimeType;
- }
-
- var activeXControlId = 'TokBox.otiePluginInstaller',
- installPluginName = 'otiePluginInstaller',
- unversionedMimeType = 'application/x-otieplugininstaller',
- plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName];
-
- installedVersion = -1;
-
- if (plugin) {
- // Look through the supported mime-types for the version
- // There should only be one mime-type in our use case, and
- // if there's more than one they should all have the same
- // version.
- var numMimeTypes = plugin.length,
- extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
- ',version=([0-9a-zA-Z-_.]+)', 'i'),
- mimeType,
- bits;
-
-
- for (var i=0; i version2;
+ }
+
+ // The versions have multiple components (i.e. 0.10.30) and
+ // must be compared piecewise.
+ // Note: I'm ignoring the case where one version has multiple
+ // components and the other doesn't.
+ var v1 = version1.split('.'),
+ v2 = version2.split('.'),
+ versionLength = (v1.length > v2.length ? v2 : v1).length;
+
+ for (var i = 0; i < versionLength; ++i) {
+ if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) {
+ return true;
+ }
+ }
+
+ // Special case, v1 has extra components but the initial components
+ // were identical, we assume this means newer but it might also mean
+ // that someone changed versioning systems.
+ if (i < v1.length) {
+ return true;
+ }
+
+ return false;
+ };
+
+ // Work out the full mimeType (including the currently installed version)
+ // of the installer.
+ var findMimeTypeAndVersion = function findMimeTypeAndVersion() {
+
+ if (updaterMimeType !== void 0) {
+ return updaterMimeType;
+ }
+
+ var activeXControlId = 'TokBox.OpenTokPluginInstaller',
+ installPluginName = 'OpenTokPluginInstaller',
+ unversionedMimeType = 'application/x-opentokplugininstaller',
+ plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName];
+
+ installedVersion = -1;
+
+ if (plugin) {
+ // Look through the supported mime-types for the version
+ // There should only be one mime-type in our use case, and
+ // if there's more than one they should all have the same
+ // version.
+ var numMimeTypes = plugin.length,
+ extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') +
+ ',version=([0-9a-zA-Z-_.]+)', 'i'),
+ mimeType,
+ bits;
+
+ for (var i = 0; i < numMimeTypes; ++i) {
+ mimeType = plugin[i];
+
+ // Look through the supported mimeTypes and find
+ // the newest one.
+ if (mimeType && mimeType.enabledPlugin &&
+ (mimeType.enabledPlugin.name === plugin.name) &&
+ mimeType.type.indexOf(unversionedMimeType) !== -1) {
+
+ bits = extractVersion.exec(mimeType.type);
+
+ if (bits !== null && versionGreaterThan(bits[1], installedVersion)) {
+ installedVersion = bits[1];
+ }
+ }
+ }
+ } else if ($.env.name === 'IE') {
+ // This may mean that the installer plugin is not installed.
+ // Although it could also mean that we're on IE 9 and below,
+ // which does not support navigator.plugins. Fallback to
+ // using 'ActiveXObject' instead.
+ try {
+ plugin = new ActiveXObject(activeXControlId);
+ installedVersion = plugin.getMasterVersion();
+ } catch (e) {}
+ }
+
+ updaterMimeType = installedVersion !== -1 ?
+ unversionedMimeType + ',version=' + installedVersion :
+ null;
+ };
+
+ var getInstallerMimeType = function getInstallerMimeType() {
+ if (updaterMimeType === void 0) {
+ findMimeTypeAndVersion();
+ }
+
+ return updaterMimeType;
+ };
+
+ var getInstalledVersion = function getInstalledVersion() {
+ if (installedVersion === void 0) {
+ findMimeTypeAndVersion();
+ }
+
+ return installedVersion;
+ };
+
+ // Version 0.4.0.4 autoupdate was broken. We want to prompt
+ // for install on 0.4.0.4 or earlier. We're also including
+ // earlier versions just in case. Version 0.4.0.10 also
+ // had a broken updater, we'll treat that version the same
+ // way.
+ var hasBrokenUpdater = function() {
+ var _broken = getInstalledVersion() === '0.4.0.9' ||
+ !versionGreaterThan(getInstalledVersion(), '0.4.0.4');
+
+ hasBrokenUpdater = function() { return _broken; };
+ return _broken;
+ };
+
+ AutoUpdater = function() {
+ var plugin;
+
+ var getControllerCurry = function getControllerFirstCurry(fn) {
+ return function() {
+ if (plugin) {
+ return fn(void 0, arguments);
+ }
+
+ PluginProxies.create({
+ mimeType: getInstallerMimeType(),
+ isVisible: false,
+ windowless: false
+ }, function(err, p) {
+ plugin = p;
+
+ if (err) {
+ OTPlugin.error('Error while loading the AutoUpdater: ' + err);
+ return;
+ }
+
+ return fn.apply(void 0, arguments);
+ });
+ };
+ };
+
+ // Returns true if the version of the plugin installed on this computer
+ // does not match the one expected by this version of OTPlugin.
+ this.isOutOfDate = function() {
+ return versionGreaterThan(OTPlugin.meta.version, getInstalledVersion());
+ };
+
+ this.autoUpdate = getControllerCurry(function() {
+ var modal = OT.Dialogs.Plugin.updateInProgress(),
+ analytics = new OT.Analytics(),
+ payload = {
+ ieVersion: $.env.version,
+ pluginOldVersion: OTPlugin.installedVersion(),
+ pluginNewVersion: OTPlugin.version()
+ };
+
+ var success = curryCallAsync(function() {
+ analytics.logEvent({
+ action: 'OTPluginAutoUpdate',
+ variation: 'Success',
+ partnerId: OT.APIKEY,
+ payload: JSON.stringify(payload)
+ });
+
+ plugin.destroy();
+
+ modal.close();
+ OT.Dialogs.Plugin.updateComplete().on({
+ reload: function() {
+ window.location.reload();
+ }
+ });
+ }),
+
+ error = curryCallAsync(function(errorCode, errorMessage, systemErrorCode) {
+ payload.errorCode = errorCode;
+ payload.systemErrorCode = systemErrorCode;
+
+ analytics.logEvent({
+ action: 'OTPluginAutoUpdate',
+ variation: 'Failure',
+ partnerId: OT.APIKEY,
+ payload: JSON.stringify(payload)
+ });
+
+ plugin.destroy();
+
+ modal.close();
+ var updateMessage = errorMessage + ' (' + errorCode +
+ '). Please restart your browser and try again.';
+
+ modal = OT.Dialogs.Plugin.updateComplete(updateMessage).on({
+ reload: function() {
+ modal.close();
+ }
+ });
+
+ OTPlugin.error('autoUpdate failed: ' + errorMessage + ' (' + errorCode +
+ '). Please restart your browser and try again.');
+ // TODO log client event
+ }),
+
+ progress = curryCallAsync(function(progress) {
+ modal.setUpdateProgress(progress.toFixed());
+ // modalBody.innerHTML = 'Updating...' + progress.toFixed() + '%';
+ });
+
+ plugin._.updatePlugin(OTPlugin.pathToInstaller(), success, error, progress);
+ });
+
+ this.destroy = function() {
+ if (plugin) plugin.destroy();
+ };
+
+ // Refresh the plugin list so that we'll hopefully detect newer versions
+ if (navigator.plugins) {
+ navigator.plugins.refresh(false);
+ }
+ };
+
+ AutoUpdater.get = function(completion) {
+ if (!autoUpdaterController) {
+ if (!this.isinstalled()) {
+ completion.call(null, 'Plugin was not installed');
+ return;
+ }
+
+ autoUpdaterController = new AutoUpdater();
+ }
+
+ completion.call(null, void 0, autoUpdaterController);
+ };
+
+ AutoUpdater.isinstalled = function() {
+ return getInstallerMimeType() !== null && !hasBrokenUpdater();
+ };
+
+ AutoUpdater.installedVersion = function() {
+ return getInstalledVersion();
+ };
+
+})();
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./proxy.js')
+// tb_require('./auto_updater.js')
+
+/* global PluginProxies */
+/* exported MediaDevices */
+
+// Exposes a enumerateDevices method and emits a devicechange event
+//
+// http://w3c.github.io/mediacapture-main/#idl-def-MediaDevices
+//
+var MediaDevices = function() {
+ var Proto = function MediaDevices() {},
+ api = new Proto();
+
+ api.enumerateDevices = function enumerateDevices(completion) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ completion(error);
+ } else {
+ PluginProxies.mediaCapturer.enumerateDevices(completion);
+ }
+ });
+ };
+
+ api.addListener = function addListener(fn, context) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ // No error message here, ready failing would have
+ // created a bunch elsewhere
+ return;
+ }
+
+ PluginProxies.mediaCapturer.on('devicesChanged', fn, context);
+ });
+ };
+
+ api.removeListener = function removeListener(fn, context) {
+ if (OTPlugin.isReady()) {
+ PluginProxies.mediaCapturer.off('devicesChanged', fn, context);
+ }
+ };
+
+ return api;
+};
+
// tb_require('./header.js')
// tb_require('./shims.js')
// tb_require('./proxy.js')
@@ -7854,32 +7895,112 @@ var createFrame = function createFrame (bodyContent, callbackId, callback) {
// tb_require('./media_stream.js')
// tb_require('./video_container.js')
// tb_require('./rumor.js')
+
+/* global scope, shim, pluginIsReady:true, PluginProxies, AutoUpdater */
+/* export registerReadyListener, notifyReadyListeners, onDomReady */
+
+var readyCallbacks = [],
+
+ // This stores the error from the load attempt. We use
+ // this if registerReadyListener gets called after a load
+ // attempt fails.
+ loadError;
+
+var // jshint -W098
+ destroy = function destroy() {
+ PluginProxies.removeAll();
+ },
+
+ registerReadyListener = function registerReadyListener(callback) {
+ if (!$.isFunction(callback)) {
+ OTPlugin.warn('registerReadyListener was called with something that was not a function.');
+ return;
+ }
+
+ if (OTPlugin.isReady()) {
+ callback.call(OTPlugin, loadError);
+ } else {
+ readyCallbacks.push(callback);
+ }
+ },
+
+ notifyReadyListeners = function notifyReadyListeners() {
+ var callback;
+
+ while ((callback = readyCallbacks.pop()) && $.isFunction(callback)) {
+ callback.call(OTPlugin, loadError);
+ }
+ },
+
+ onDomReady = function onDomReady() {
+ AutoUpdater.get(function(err, updater) {
+ if (err) {
+ loadError = 'Error while loading the AutoUpdater: ' + err;
+ notifyReadyListeners();
+ return;
+ }
+
+ // If the plugin is out of date then we kick off the
+ // auto update process and then bail out.
+ if (updater.isOutOfDate()) {
+ updater.autoUpdate();
+ return;
+ }
+
+ // Inject the controller object into the page, wait for it to load or timeout...
+ PluginProxies.createMediaCapturer(function(err) {
+ loadError = err;
+
+ if (!loadError && (!PluginProxies.mediaCapturer ||
+ !PluginProxies.mediaCapturer.isValid())) {
+ loadError = 'The TB Plugin failed to load properly';
+ }
+
+ pluginIsReady = true;
+ notifyReadyListeners();
+
+ $.onDOMUnload(destroy);
+ });
+ });
+ };
+
+// tb_require('./header.js')
+// tb_require('./shims.js')
+// tb_require('./proxy.js')
+// tb_require('./auto_updater.js')
+// tb_require('./media_constraints.js')
+// tb_require('./peer_connection.js')
+// tb_require('./media_stream.js')
+// tb_require('./media_devices.js')
+// tb_require('./video_container.js')
+// tb_require('./rumor.js')
// tb_require('./lifecycle.js')
/* global AutoUpdater,
RumorSocket,
MediaConstraints, PeerConnection, MediaStream,
registerReadyListener,
- PluginProxies */
+ PluginProxies,
+ MediaDevices */
-OTPlugin.isInstalled = function isInstalled () {
+OTPlugin.isInstalled = function isInstalled() {
if (!this.isSupported()) return false;
return AutoUpdater.isinstalled();
};
-OTPlugin.version = function version () {
+OTPlugin.version = function version() {
return OTPlugin.meta.version;
};
-OTPlugin.installedVersion = function installedVersion () {
+OTPlugin.installedVersion = function installedVersion() {
return AutoUpdater.installedVersion();
};
// Returns a URI to the OTPlugin installer that is paired with
// this version of OTPlugin.js.
-OTPlugin.pathToInstaller = function pathToInstaller () {
+OTPlugin.pathToInstaller = function pathToInstaller() {
return 'https://s3.amazonaws.com/otplugin.tokbox.com/v' +
- OTPlugin.meta.version + '/otiePluginMain.msi';
+ OTPlugin.meta.version + '/OpenTokPluginMain.msi';
};
// Trigger +callback+ when the plugin is ready
@@ -7887,19 +8008,8 @@ OTPlugin.pathToInstaller = function pathToInstaller () {
// Most of the public API cannot be called until
// the plugin is ready.
//
-OTPlugin.ready = function ready (callback) {
- if (OTPlugin.isReady()) {
- var err;
-
- if (!PluginProxies.mediaCapturer || !PluginProxies.mediaCapturer.isValid()) {
- err = 'The TB Plugin failed to load properly';
- }
-
- callback.call(OTPlugin, err);
- }
- else {
- registerReadyListener(callback);
- }
+OTPlugin.ready = function ready(callback) {
+ registerReadyListener(callback);
};
// Helper function for OTPlugin.getUserMedia
@@ -7919,13 +8029,12 @@ var _getUserMedia = function _getUserMedia(mediaConstraints, success, error) {
// Equivalent to: window.getUserMedia(constraints, success, error);
//
// Except that the constraints won't be identical
-OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) {
+OTPlugin.getUserMedia = function getUserMedia(userConstraints, success, error) {
var constraints = new MediaConstraints(userConstraints);
if (constraints.screenSharing) {
_getUserMedia(constraints, success, error);
- }
- else {
+ } else {
var sources = [];
if (constraints.hasVideo) sources.push('video');
if (constraints.hasAudio) sources.push('audio');
@@ -7946,9 +8055,19 @@ OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error)
}
};
+OTPlugin.enumerateDevices = function(completion) {
+ OTPlugin.ready(function(error) {
+ if (error) {
+ completion(error);
+ } else {
+ PluginProxies.mediaCapturer.enumerateDevices(completion);
+ }
+ });
+};
+
OTPlugin.initRumorSocket = function(messagingURL, completion) {
OTPlugin.ready(function(error) {
- if(error) {
+ if (error) {
completion(error);
} else {
completion(null, new RumorSocket(PluginProxies.mediaCapturer, messagingURL));
@@ -7956,14 +8075,13 @@ OTPlugin.initRumorSocket = function(messagingURL, completion) {
});
};
-
// Equivalent to: var pc = new window.RTCPeerConnection(iceServers, options);
//
// Except that it is async and takes a completion handler
-OTPlugin.initPeerConnection = function initPeerConnection (iceServers,
- options,
- localStream,
- completion) {
+OTPlugin.initPeerConnection = function initPeerConnection(iceServers,
+ options,
+ localStream,
+ completion) {
var gotPeerObject = function(err, plugin) {
if (err) {
@@ -7991,25 +8109,27 @@ OTPlugin.initPeerConnection = function initPeerConnection (iceServers,
// a new one.
if (localStream && localStream._.plugin) {
gotPeerObject(null, localStream._.plugin);
- }
- else {
+ } else {
PluginProxies.createMediaPeer(gotPeerObject);
}
};
// A RTCSessionDescription like object exposed for native WebRTC compatability
-OTPlugin.RTCSessionDescription = function RTCSessionDescription (options) {
+OTPlugin.RTCSessionDescription = function RTCSessionDescription(options) {
this.type = options.type;
this.sdp = options.sdp;
};
// A RTCIceCandidate like object exposed for native WebRTC compatability
-OTPlugin.RTCIceCandidate = function RTCIceCandidate (options) {
+OTPlugin.RTCIceCandidate = function RTCIceCandidate(options) {
this.sdpMid = options.sdpMid;
this.sdpMLineIndex = parseInt(options.sdpMLineIndex, 10);
this.candidate = options.candidate;
};
+OTPlugin.mediaDevices = new MediaDevices();
+
+
// tb_require('./api.js')
/* global shim, onDomReady */
@@ -8024,6 +8144,72 @@ $.onDOMLoad(onDomReady);
+/* exported _setCertificates */
+
+var _setCertificates = function(pcConfig, completion) {
+ if (
+ OT.$.env.name === 'Firefox' &&
+ window.mozRTCPeerConnection &&
+ window.mozRTCPeerConnection.generateCertificate
+ ) {
+ window.mozRTCPeerConnection.generateCertificate({
+ name: 'RSASSA-PKCS1-v1_5',
+ hash: 'SHA-256',
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ }).catch(function(err) {
+ completion(err);
+ }).then(function(cert) {
+ pcConfig.certificates = [cert];
+ completion(undefined, pcConfig);
+ });
+ } else {
+ OT.$.callAsync(function() {
+ completion(undefined, pcConfig);
+ });
+ }
+};
+
+/* exported videoContentResizesMixin */
+
+var videoContentResizesMixin = function(self, domElement) {
+
+ var width = domElement.videoWidth,
+ height = domElement.videoHeight,
+ stopped = true;
+
+ function actor() {
+ if (stopped) {
+ return;
+ }
+ if (width !== domElement.videoWidth || height !== domElement.videoHeight) {
+ self.trigger('videoDimensionsChanged',
+ { width: width, height: height },
+ { width: domElement.videoWidth, height: domElement.videoHeight }
+ );
+ width = domElement.videoWidth;
+ height = domElement.videoHeight;
+ }
+ waiter();
+ }
+
+ function waiter() {
+ self.whenTimeIncrements(function() {
+ window.requestAnimationFrame(actor);
+ });
+ }
+
+ self.startObservingSize = function() {
+ stopped = false;
+ waiter();
+ };
+
+ self.stopObservingSize = function() {
+ stopped = true;
+ };
+
+};
+
/* jshint ignore:start */
!(function(window, OT) {
/* jshint ignore:end */
@@ -8045,9 +8231,9 @@ if (location.protocol === 'file:') {
var OT = window.OT || {};
// Define the APIKEY this is a global parameter which should not change
-OT.APIKEY = (function(){
+OT.APIKEY = (function() {
// Script embed
- var scriptSrc = (function(){
+ var scriptSrc = (function() {
var s = document.getElementsByTagName('script');
s = s[s.length - 1];
s = s.getAttribute('src') || s.src;
@@ -8058,15 +8244,14 @@ OT.APIKEY = (function(){
return m ? m[1] : '';
})();
-
if (!window.OT) window.OT = OT;
if (!window.TB) window.TB = OT;
// tb_require('../js/ot.js')
OT.properties = {
- version: 'v2.5.2', // The current version (eg. v2.0.4) (This is replaced by gradle)
- build: 'f4508e1', // The current build hash (This is replaced by gradle)
+ version: 'v2.6.8', // The current version (eg. v2.0.4) (This is replaced by gradle)
+ build: 'fae7901', // The current build hash (This is replaced by gradle)
// Whether or not to turn on debug logging by default
debug: 'false',
@@ -8097,12 +8282,11 @@ OT.properties = {
apiURLSSL: 'https://anvil.opentok.com',
minimumVersion: {
- firefox: parseFloat('29'),
- chrome: parseFloat('34')
+ firefox: parseFloat('37'),
+ chrome: parseFloat('39')
}
};
-
// tb_require('../ot.js')
// tb_require('../../conf/properties.js');
@@ -8110,7 +8294,6 @@ OT.properties = {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
// Mount OTHelpers on OT.$
OT.$ = window.OTHelpers;
@@ -8125,7 +8308,7 @@ OT.$.defineGetters = function(self, getters, enumerable) {
if (enumerable === void 0) enumerable = false;
for (var key in getters) {
- if(!getters.hasOwnProperty(key)) {
+ if (!getters.hasOwnProperty(key)) {
continue;
}
@@ -8169,7 +8352,6 @@ OT.setLogLevel = function(level) {
var debugTrue = OT.properties.debug === 'true' || OT.properties.debug === true;
OT.setLogLevel(debugTrue ? OT.DEBUG : OT.ERROR);
-
// Patch the userAgent to ref OTPlugin, if it's installed.
if (OTPlugin && OTPlugin.isInstalled()) {
OT.$.env.userAgent += '; OTPlugin ' + OTPlugin.version();
@@ -8178,7 +8360,6 @@ if (OTPlugin && OTPlugin.isInstalled()) {
// @todo remove this
OT.$.userAgent = function() { return OT.$.env.userAgent; };
-
/**
* Sets the API log level.
*
@@ -8253,6 +8434,122 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; };
// tb_require('../../../helpers/helpers.js')
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global OT */
+
+// Rumor Messaging for JS
+//
+// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork
+//
+// @todo Rumor {
+// Add error codes for all the error cases
+// Add Dependability commands
+// }
+
+OT.Rumor = {
+ MessageType: {
+ // This is used to subscribe to address/addresses. The address/addresses the
+ // client specifies here is registered on the server. Once any message is sent to
+ // that address/addresses, the client receives that message.
+ SUBSCRIBE: 0,
+
+ // This is used to unsubscribe to address / addresses. Once the client unsubscribe
+ // to an address, it will stop getting messages sent to that address.
+ UNSUBSCRIBE: 1,
+
+ // This is used to send messages to arbitrary address/ addresses. Messages can be
+ // anything and Rumor will not care about what is included.
+ MESSAGE: 2,
+
+ // This will be the first message that the client sends to the server. It includes
+ // the uniqueId for that client connection and a disconnect_notify address that will
+ // be notified once the client disconnects.
+ CONNECT: 3,
+
+ // This will be the message used by the server to notify an address that a
+ // client disconnected.
+ DISCONNECT: 4,
+
+ //Enhancements to support Keepalives
+ PING: 7,
+ PONG: 8,
+ STATUS: 9
+ }
+};
+
+// tb_require('../../../helpers/helpers.js')
+// tb_require('./rumor.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global OT, OTPlugin */
+
+!(function() {
+
+ OT.Rumor.PluginSocket = function(messagingURL, events) {
+
+ var webSocket,
+ state = 'initializing';
+
+ OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) {
+ if (err) {
+ state = 'closed';
+ events.onClose({ code: 4999 });
+ } else if (state === 'initializing') {
+ webSocket = rumorSocket;
+
+ webSocket.onOpen(function() {
+ state = 'open';
+ events.onOpen();
+ });
+ webSocket.onClose(function(error) {
+ state = 'closed'; /* CLOSED */
+ events.onClose({ code: error });
+ });
+ webSocket.onError(function(error) {
+ state = 'closed'; /* CLOSED */
+ events.onError(error);
+ /* native websockets seem to do this, so should we */
+ events.onClose({ code: error });
+ });
+
+ webSocket.onMessage(function(type, addresses, headers, payload) {
+ var msg = new OT.Rumor.Message(type, addresses, headers, payload);
+ events.onMessage(msg);
+ });
+
+ webSocket.open();
+ } else {
+ this.close();
+ }
+ }, this));
+
+ this.close = function() {
+ if (state === 'initializing' || state === 'closed') {
+ state = 'closed';
+ return;
+ }
+
+ webSocket.close(1000, '');
+ };
+
+ this.send = function(msg) {
+ if (state === 'open') {
+ webSocket.send(msg);
+ }
+ };
+
+ this.isClosed = function() {
+ return state === 'closed';
+ };
+
+ };
+
+}(this));
+
+// tb_require('../../../helpers/helpers.js')
+
// https://code.google.com/p/stringencoding/
// An implementation of http://encoding.spec.whatwg.org/#api
// Modified by TokBox to remove all encoding support except for utf-8
@@ -8279,11 +8576,11 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; };
(function(global) {
'use strict';
- if(OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) {
+ if (OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) {
return; // IE 8 doesn't do websockets. No websockets, no encoding.
}
- if ( (global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) {
+ if ((global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) {
// defer to the native ones
return;
}
@@ -8313,7 +8610,6 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; };
return Math.floor(n / d);
}
-
//
// Implementation of Encoding specification
// http://dvcs.w3.org/hg/encoding/raw-file/tip/Overview.html
@@ -9308,122 +9604,6 @@ OT.$.userAgent = function() { return OT.$.env.userAgent; };
}(this));
-// tb_require('../../../helpers/helpers.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OT */
-
-// Rumor Messaging for JS
-//
-// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork
-//
-// @todo Rumor {
-// Add error codes for all the error cases
-// Add Dependability commands
-// }
-
-OT.Rumor = {
- MessageType: {
- // This is used to subscribe to address/addresses. The address/addresses the
- // client specifies here is registered on the server. Once any message is sent to
- // that address/addresses, the client receives that message.
- SUBSCRIBE: 0,
-
- // This is used to unsubscribe to address / addresses. Once the client unsubscribe
- // to an address, it will stop getting messages sent to that address.
- UNSUBSCRIBE: 1,
-
- // This is used to send messages to arbitrary address/ addresses. Messages can be
- // anything and Rumor will not care about what is included.
- MESSAGE: 2,
-
- // This will be the first message that the client sends to the server. It includes
- // the uniqueId for that client connection and a disconnect_notify address that will
- // be notified once the client disconnects.
- CONNECT: 3,
-
- // This will be the message used by the server to notify an address that a
- // client disconnected.
- DISCONNECT: 4,
-
- //Enhancements to support Keepalives
- PING: 7,
- PONG: 8,
- STATUS: 9
- }
-};
-
-// tb_require('../../../helpers/helpers.js')
-// tb_require('./rumor.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OT, OTPlugin */
-
-!(function() {
-
- OT.Rumor.PluginSocket = function(messagingURL, events) {
-
- var webSocket,
- state = 'initializing';
-
- OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) {
- if(err) {
- state = 'closed';
- events.onClose({ code: 4999 });
- } else if(state === 'initializing') {
- webSocket = rumorSocket;
-
- webSocket.onOpen(function() {
- state = 'open';
- events.onOpen();
- });
- webSocket.onClose(function(error) {
- state = 'closed'; /* CLOSED */
- events.onClose({ code: error });
- });
- webSocket.onError(function(error) {
- state = 'closed'; /* CLOSED */
- events.onError(error);
- /* native websockets seem to do this, so should we */
- events.onClose({ code: error });
- });
-
- webSocket.onMessage(function(type, addresses, headers, payload) {
- var msg = new OT.Rumor.Message(type, addresses, headers, payload);
- events.onMessage(msg);
- });
-
- webSocket.open();
- } else {
- this.close();
- }
- }, this));
-
- this.close = function() {
- if(state === 'initializing' || state === 'closed') {
- state = 'closed';
- return;
- }
-
- webSocket.close(1000, '');
- };
-
- this.send = function(msg) {
- if(state === 'open') {
- webSocket.send(msg);
- }
- };
-
- this.isClosed = function() {
- return state === 'closed';
- };
-
- };
-
-}(this));
-
// tb_require('../../../helpers/helpers.js')
// tb_require('./encoding.js')
// tb_require('./rumor.js')
@@ -9438,7 +9618,7 @@ OT.Rumor = {
// * https://tbwiki.tokbox.com/index.php/Rumor_Message_Packet
// * https://tbwiki.tokbox.com/index.php/Rumor_Protocol
//
-OT.Rumor.Message = function (type, toAddress, headers, data) {
+OT.Rumor.Message = function(type, toAddress, headers, data) {
this.type = type;
this.toAddress = toAddress;
this.headers = headers;
@@ -9449,7 +9629,7 @@ OT.Rumor.Message = function (type, toAddress, headers, data) {
this.isError = !(this.status && this.status[0] === '2');
};
-OT.Rumor.Message.prototype.serialize = function () {
+OT.Rumor.Message.prototype.serialize = function() {
var offset = 8,
cBuf = 7,
address = [],
@@ -9478,7 +9658,7 @@ OT.Rumor.Message.prototype.serialize = function () {
i = 0;
for (var key in this.headers) {
- if(!this.headers.hasOwnProperty(key)) {
+ if (!this.headers.hasOwnProperty(key)) {
continue;
}
headerKey.push(new TextEncoder('utf-8').encode(key));
@@ -9560,9 +9740,9 @@ function toArrayBuffer(buffer) {
return ab;
}
-OT.Rumor.Message.deserialize = function (buffer) {
+OT.Rumor.Message.deserialize = function(buffer) {
- if(typeof Buffer !== 'undefined' &&
+ if (typeof Buffer !== 'undefined' &&
Buffer.isBuffer(buffer)) {
buffer = toArrayBuffer(buffer);
}
@@ -9620,8 +9800,7 @@ OT.Rumor.Message.deserialize = function (buffer) {
return new OT.Rumor.Message(type, address, headers, data);
};
-
-OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) {
+OT.Rumor.Message.Connect = function(uniqueId, notifyDisconnectAddress) {
var headers = {
uniqueId: uniqueId,
notifyDisconnectAddress: notifyDisconnectAddress
@@ -9630,7 +9809,7 @@ OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) {
return new OT.Rumor.Message(OT.Rumor.MessageType.CONNECT, [], headers, '');
};
-OT.Rumor.Message.Disconnect = function () {
+OT.Rumor.Message.Disconnect = function() {
return new OT.Rumor.Message(OT.Rumor.MessageType.DISCONNECT, [], {}, '');
};
@@ -9643,7 +9822,7 @@ OT.Rumor.Message.Unsubscribe = function(topics) {
};
OT.Rumor.Message.Publish = function(topics, message, headers) {
- return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers||{}, message || '');
+ return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers || {}, message || '');
};
// This message is used to implement keepalives on the persistent
@@ -9697,7 +9876,7 @@ OT.Rumor.Message.Ping = function() {
// the socket. If the buffer doesn't drain after a certain length of time
// we give up and close it anyway.
disconnectWhenSendBufferIsDrained =
- function disconnectWhenSendBufferIsDrained (bufferDrainRetries) {
+ function disconnectWhenSendBufferIsDrained(bufferDrainRetries) {
if (!webSocket) return;
if (bufferDrainRetries === void 0) bufferDrainRetries = 0;
@@ -9706,7 +9885,7 @@ OT.Rumor.Message.Ping = function() {
if (webSocket.bufferedAmount > 0 &&
(bufferDrainRetries + 1) <= BUFFER_DRAIN_MAX_RETRIES) {
bufferDrainTimeout = setTimeout(disconnectWhenSendBufferIsDrained,
- BUFFER_DRAIN_INTERVAL, bufferDrainRetries+1);
+ BUFFER_DRAIN_INTERVAL, bufferDrainRetries + 1);
} else {
close();
@@ -9735,7 +9914,6 @@ OT.Rumor.Message.Ping = function() {
};
-
}(this));
// tb_require('../../../helpers/helpers.js')
@@ -9752,9 +9930,10 @@ var WEB_SOCKET_KEEP_ALIVE_INTERVAL = 9000,
// Magic Connectivity Timeout Constant: We wait 9*the keep alive interval,
// on the third keep alive we trigger the timeout if we haven't received the
// server pong.
- WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5*WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100,
+ WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5 * WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100,
- wsCloseErrorCodes;
+ wsCloseErrorCodes,
+ errorMap;
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Close_codes
// http://docs.oracle.com/javaee/7/api/javax/websocket/CloseReason.CloseCodes.html
@@ -9783,8 +9962,22 @@ wsCloseErrorCodes = {
'unexpected condition that prevented it from fulfilling the request',
// .... codes in the 4000-4999 range are available for use by applications.
- 4001: 'Connectivity loss was detected as it was too long since the socket received the ' +
- 'last PONG message'
+ 4001: 'Connectivity loss was detected as it was too long since the socket received the ' +
+ 'last PONG message',
+ 4010: 'Timed out while waiting for the Rumor socket to connect.',
+ 4020: 'Error code unavailable.',
+ 4030: 'Exception was thrown during Rumor connection, possibly because of a blocked port.'
+};
+
+errorMap = {
+ CLOSE_PROTOCOL_ERROR: 1002,
+ CLOSE_UNSUPPORTED: 1003,
+ CLOSE_TOO_LARGE: 1004,
+ CLOSE_NO_STATUS: 1005,
+ CLOSE_ABNORMAL: 1006,
+ CLOSE_TIMEOUT: 4010,
+ CLOSE_FALLBACK_CODE: 4020,
+ CLOSE_CONNECT_EXCEPTION: 4030
};
OT.Rumor.SocketError = function(code, message) {
@@ -9796,6 +9989,8 @@ OT.Rumor.SocketError = function(code, message) {
// so in normal operation you would omit it.
OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) {
+ var _this = this;
+
var states = ['disconnected', 'error', 'connected', 'connecting', 'disconnecting'],
webSocket,
id,
@@ -9808,7 +10003,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
lastMessageTimestamp, // The timestamp of the last message received
keepAliveTimer; // Timer for the connectivity checks
-
//// Private API
var stateChanged = function(newState) {
switch (newState) {
@@ -9817,7 +10011,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
webSocket = null;
if (onClose) {
var error;
- if(hasLostConnectivity()) {
+ if (hasLostConnectivity()) {
error = new Error(wsCloseErrorCodes[4001]);
error.code = 4001;
}
@@ -9829,60 +10023,63 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
setState = OT.$.statable(this, states, 'disconnected', stateChanged),
- validateCallback = function validateCallback (name, callback) {
- if (callback === null || !OT.$.isFunction(callback) ) {
+ validateCallback = function validateCallback(name, callback) {
+ if (callback === null || !OT.$.isFunction(callback)) {
throw new Error('The Rumor.Socket ' + name +
' callback must be a valid function or null');
}
},
- error = OT.$.bind(function error (errorMessage) {
- OT.error('Rumor.Socket: ' + errorMessage);
+ raiseError = function raiseError(code, extraDetail) {
+ code = code || errorMap.CLOSE_FALLBACK_CODE;
- var socketError = new OT.Rumor.SocketError(null, errorMessage || 'Unknown Socket Error');
+ var messageFromCode = wsCloseErrorCodes[code] || 'No message available from code.';
+ var message = messageFromCode + (extraDetail ? ' ' + extraDetail : '');
+
+ OT.error('Rumor.Socket: ' + message);
+
+ var socketError = new OT.Rumor.SocketError(code, message);
if (connectTimeout) clearTimeout(connectTimeout);
setState('error');
- if (this.previousState === 'connecting' && connectCallback) {
+ if (_this.previousState === 'connecting' && connectCallback) {
connectCallback(socketError, void 0);
connectCallback = null;
}
if (onError) onError(socketError);
- }, this),
+ },
- hasLostConnectivity = function hasLostConnectivity () {
+ hasLostConnectivity = function hasLostConnectivity() {
if (!lastMessageTimestamp) return false;
return (OT.$.now() - lastMessageTimestamp) >= WEB_SOCKET_CONNECTIVITY_TIMEOUT;
},
- sendKeepAlive = OT.$.bind(function() {
- if (!this.is('connected')) return;
+ sendKeepAlive = function() {
+ if (!_this.is('connected')) return;
- if ( hasLostConnectivity() ) {
+ if (hasLostConnectivity()) {
webSocketDisconnected({code: 4001});
- }
- else {
+ } else {
webSocket.send(OT.Rumor.Message.Ping());
keepAliveTimer = setTimeout(sendKeepAlive, WEB_SOCKET_KEEP_ALIVE_INTERVAL);
}
- }, this),
+ },
// Returns true if we think the DOM has been unloaded
// It detects this by looking for the OT global, which
// should always exist until the DOM is cleaned up.
- isDOMUnloaded = function isDOMUnloaded () {
+ isDOMUnloaded = function isDOMUnloaded() {
return !window.OT;
};
-
//// Private Event Handlers
- var webSocketConnected = OT.$.bind(function webSocketConnected () {
+ var webSocketConnected = function webSocketConnected() {
if (connectTimeout) clearTimeout(connectTimeout);
- if (this.isNot('connecting')) {
+ if (_this.isNot('connecting')) {
OT.debug('webSocketConnected reached in state other than connecting');
return;
}
@@ -9905,21 +10102,21 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
lastMessageTimestamp = OT.$.now();
sendKeepAlive();
}, WEB_SOCKET_KEEP_ALIVE_INTERVAL);
- }, this),
+ },
- webSocketConnectTimedOut = function webSocketConnectTimedOut () {
+ webSocketConnectTimedOut = function webSocketConnectTimedOut() {
var webSocketWas = webSocket;
- error('Timed out while waiting for the Rumor socket to connect.');
+ raiseError(errorMap.CLOSE_TIMEOUT);
// This will prevent a socket eventually connecting
// But call it _after_ the error just in case any of
// the callbacks fire synchronously, breaking the error
// handling code.
try {
webSocketWas.close();
- } catch(x) {}
+ } catch (x) {}
},
- webSocketError = function webSocketError () {},
+ webSocketError = function webSocketError() {},
// var errorMessage = 'Unknown Socket Error';
// @fixme We MUST be able to do better than this!
@@ -9930,7 +10127,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
// until the socket is closed.
// error(errorMessage);
- webSocketDisconnected = OT.$.bind(function webSocketDisconnected (closeEvent) {
+ webSocketDisconnected = function webSocketDisconnected(closeEvent) {
if (connectTimeout) clearTimeout(connectTimeout);
if (keepAliveTimer) clearTimeout(keepAliveTimer);
@@ -9943,18 +10140,20 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
}
if (closeEvent.code !== 1000 && closeEvent.code !== 1001) {
- var reason = closeEvent.reason || closeEvent.message;
- if (!reason && wsCloseErrorCodes.hasOwnProperty(closeEvent.code)) {
- reason = wsCloseErrorCodes[closeEvent.code];
+ if (closeEvent.code) {
+ raiseError(closeEvent.code);
+ } else {
+ raiseError(
+ errorMap.CLOSE_FALLBACK_CODE,
+ closeEvent.reason || closeEvent.message
+ );
}
-
- error('Rumor Socket Disconnected: ' + reason);
}
- if (this.isNot('error')) setState('disconnected');
- }, this),
+ if (_this.isNot('error')) setState('disconnected');
+ },
- webSocketReceivedMessage = function webSocketReceivedMessage (msg) {
+ webSocketReceivedMessage = function webSocketReceivedMessage(msg) {
lastMessageTimestamp = OT.$.now();
if (onMessage) {
@@ -9964,10 +10163,9 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
}
};
-
//// Public API
- this.publish = function (topics, message, headers) {
+ this.publish = function(topics, message, headers) {
webSocket.send(OT.Rumor.Message.Publish(topics, message, headers));
};
@@ -9979,7 +10177,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
webSocket.send(OT.Rumor.Message.Unsubscribe(topics));
};
- this.connect = function (connectionId, complete) {
+ this.connect = function(connectionId, complete) {
if (this.is('connecting', 'connected')) {
complete(new OT.Rumor.SocketError(null,
'Rumor.Socket cannot connect when it is already connecting or connected.'));
@@ -10001,19 +10199,17 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
};
try {
- if(typeof TheWebSocket !== 'undefined') {
+ if (typeof TheWebSocket !== 'undefined') {
webSocket = new OT.Rumor.NativeSocket(TheWebSocket, messagingURL, events);
} else {
webSocket = new OT.Rumor.PluginSocket(messagingURL, events);
}
connectTimeout = setTimeout(webSocketConnectTimedOut, OT.Rumor.Socket.CONNECT_TIMEOUT);
- }
- catch(e) {
+ } catch (e) {
OT.error(e);
- // @todo add an actual error message
- error('Could not connect to the Rumor socket, possibly because of a blocked port.');
+ raiseError(errorMap.CLOSE_CONNECT_EXCEPTION);
}
};
@@ -10028,8 +10224,7 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
if (webSocket.isClosed()) {
if (this.isNot('error')) setState('disconnected');
- }
- else {
+ } else {
if (this.is('connected')) {
// Look! We are nice to the rumor server ;-)
webSocket.send(OT.Rumor.Message.Disconnect());
@@ -10040,8 +10235,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
}
};
-
-
OT.$.defineProperties(this, {
id: {
get: function() { return id; }
@@ -10088,7 +10281,6 @@ OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket)
// The number of ms to wait for the websocket to connect
OT.Rumor.Socket.CONNECT_TIMEOUT = 15000;
-
// tb_require('../../../helpers/helpers.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
@@ -10197,11 +10389,10 @@ OT.Raptor = {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-OT.Raptor.serializeMessage = function (message) {
+OT.Raptor.serializeMessage = function(message) {
return JSON.stringify(message);
};
-
// Deserialising a Raptor message mainly means doing a JSON.parse on it.
// We do decorate the final message with a few extra helper properies though.
//
@@ -10211,7 +10402,7 @@ OT.Raptor.serializeMessage = function (message) {
// * signature: typeName and actionName combined. This is mainly for debugging. E.g. A type
// of 102 and an action of 101 would result in a signature of "STREAM:CREATE"
//
-OT.Raptor.deserializeMessage = function (msg) {
+OT.Raptor.deserializeMessage = function(msg) {
if (msg.length === 0) return {};
var message = JSON.parse(msg),
@@ -10219,28 +10410,27 @@ OT.Raptor.deserializeMessage = function (msg) {
// Remove the Raptor protocol version
bits.shift();
- if (bits[bits.length-1] === '') bits.pop();
+ if (bits[bits.length - 1] === '') bits.pop();
message.params = {};
- for (var i=0, numBits=bits.length ; i 6) {
- message.resource = bits[bits.length-4] + '_' + bits[bits.length-2];
+ if (bits[bits.length - 2] === 'channel' && bits.length > 6) {
+ message.resource = bits[bits.length - 4] + '_' + bits[bits.length - 2];
} else {
- message.resource = bits[bits.length-2];
+ message.resource = bits[bits.length - 2];
}
- }
- else {
- if (bits[bits.length-1] === 'channel' && bits.length > 5) {
- message.resource = bits[bits.length-3] + '_' + bits[bits.length-1];
+ } else {
+ if (bits[bits.length - 1] === 'channel' && bits.length > 5) {
+ message.resource = bits[bits.length - 3] + '_' + bits[bits.length - 1];
} else {
- message.resource = bits[bits.length-1];
+ message.resource = bits[bits.length - 1];
}
}
@@ -10248,7 +10438,7 @@ OT.Raptor.deserializeMessage = function (msg) {
return message;
};
-OT.Raptor.unboxFromRumorMessage = function (rumorMessage) {
+OT.Raptor.unboxFromRumorMessage = function(rumorMessage) {
var message = OT.Raptor.deserializeMessage(rumorMessage.data);
message.transactionId = rumorMessage.transactionId;
message.fromAddress = rumorMessage.headers['X-TB-FROM-ADDRESS'];
@@ -10256,7 +10446,7 @@ OT.Raptor.unboxFromRumorMessage = function (rumorMessage) {
return message;
};
-OT.Raptor.parseIceServers = function (message) {
+OT.Raptor.parseIceServers = function(message) {
try {
return JSON.parse(message.data).content.iceServers;
} catch (e) {
@@ -10266,8 +10456,7 @@ OT.Raptor.parseIceServers = function (message) {
OT.Raptor.Message = {};
-
-OT.Raptor.Message.offer = function (uri, offerSdp) {
+OT.Raptor.Message.offer = function(uri, offerSdp) {
return OT.Raptor.serializeMessage({
method: 'offer',
uri: uri,
@@ -10277,10 +10466,9 @@ OT.Raptor.Message.offer = function (uri, offerSdp) {
});
};
-
OT.Raptor.Message.connections = {};
-OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId) {
+OT.Raptor.Message.connections.create = function(apiKey, sessionId, connectionId) {
return OT.Raptor.serializeMessage({
method: 'create',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId,
@@ -10290,7 +10478,7 @@ OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId
});
};
-OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionId) {
+OT.Raptor.Message.connections.destroy = function(apiKey, sessionId, connectionId) {
return OT.Raptor.serializeMessage({
method: 'delete',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId,
@@ -10298,10 +10486,9 @@ OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionI
});
};
-
OT.Raptor.Message.sessions = {};
-OT.Raptor.Message.sessions.get = function (apiKey, sessionId) {
+OT.Raptor.Message.sessions.get = function(apiKey, sessionId) {
return OT.Raptor.serializeMessage({
method: 'read',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId,
@@ -10309,10 +10496,9 @@ OT.Raptor.Message.sessions.get = function (apiKey, sessionId) {
});
};
-
OT.Raptor.Message.streams = {};
-OT.Raptor.Message.streams.get = function (apiKey, sessionId, streamId) {
+OT.Raptor.Message.streams.get = function(apiKey, sessionId, streamId) {
return OT.Raptor.serializeMessage({
method: 'read',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId,
@@ -10341,7 +10527,7 @@ OT.Raptor.Message.streams.channelFromOTChannel = function(channel) {
return raptorChannel;
};
-OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name,
+OT.Raptor.Message.streams.create = function(apiKey, sessionId, streamId, name,
audioFallbackEnabled, channels, minBitrate, maxBitrate) {
var messageContent = {
id: streamId,
@@ -10362,7 +10548,7 @@ OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name,
});
};
-OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) {
+OT.Raptor.Message.streams.destroy = function(apiKey, sessionId, streamId) {
return OT.Raptor.serializeMessage({
method: 'delete',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId,
@@ -10370,8 +10556,7 @@ OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) {
});
};
-
-OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answerSdp) {
+OT.Raptor.Message.streams.answer = function(apiKey, sessionId, streamId, answerSdp) {
return OT.Raptor.serializeMessage({
method: 'answer',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId,
@@ -10381,7 +10566,7 @@ OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answer
});
};
-OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, candidate) {
+OT.Raptor.Message.streams.candidate = function(apiKey, sessionId, streamId, candidate) {
return OT.Raptor.serializeMessage({
method: 'candidate',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId,
@@ -10391,7 +10576,7 @@ OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, can
OT.Raptor.Message.streamChannels = {};
OT.Raptor.Message.streamChannels.update =
- function (apiKey, sessionId, streamId, channelId, attributes) {
+ function(apiKey, sessionId, streamId, channelId, attributes) {
return OT.Raptor.serializeMessage({
method: 'update',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' +
@@ -10400,11 +10585,10 @@ OT.Raptor.Message.streamChannels.update =
});
};
-
OT.Raptor.Message.subscribers = {};
OT.Raptor.Message.subscribers.create =
- function (apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) {
+ function(apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) {
var content = {
id: subscriberId,
connection: connectionId,
@@ -10422,7 +10606,7 @@ OT.Raptor.Message.subscribers.create =
});
};
-OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, subscriberId) {
+OT.Raptor.Message.subscribers.destroy = function(apiKey, sessionId, streamId, subscriberId) {
return OT.Raptor.serializeMessage({
method: 'delete',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
@@ -10432,7 +10616,7 @@ OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, s
};
OT.Raptor.Message.subscribers.update =
- function (apiKey, sessionId, streamId, subscriberId, attributes) {
+ function(apiKey, sessionId, streamId, subscriberId, attributes) {
return OT.Raptor.serializeMessage({
method: 'update',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
@@ -10441,9 +10625,8 @@ OT.Raptor.Message.subscribers.update =
});
};
-
OT.Raptor.Message.subscribers.candidate =
- function (apiKey, sessionId, streamId, subscriberId, candidate) {
+ function(apiKey, sessionId, streamId, subscriberId, candidate) {
return OT.Raptor.serializeMessage({
method: 'candidate',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
@@ -10452,9 +10635,8 @@ OT.Raptor.Message.subscribers.candidate =
});
};
-
OT.Raptor.Message.subscribers.answer =
- function (apiKey, sessionId, streamId, subscriberId, answerSdp) {
+ function(apiKey, sessionId, streamId, subscriberId, answerSdp) {
return OT.Raptor.serializeMessage({
method: 'answer',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
@@ -10465,11 +10647,10 @@ OT.Raptor.Message.subscribers.answer =
});
};
-
OT.Raptor.Message.subscriberChannels = {};
OT.Raptor.Message.subscriberChannels.update =
- function (apiKey, sessionId, streamId, subscriberId, channelId, attributes) {
+ function(apiKey, sessionId, streamId, subscriberId, channelId, attributes) {
return OT.Raptor.serializeMessage({
method: 'update',
uri: '/v2/partner/' + apiKey + '/session/' + sessionId +
@@ -10478,10 +10659,9 @@ OT.Raptor.Message.subscriberChannels.update =
});
};
-
OT.Raptor.Message.signals = {};
-OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, data) {
+OT.Raptor.Message.signals.create = function(apiKey, sessionId, toAddress, type, data) {
var content = {};
if (type !== void 0) content.type = type;
if (data !== void 0) content.data = data;
@@ -10497,7 +10677,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
// tb_require('../../../helpers/helpers.js')
// tb_require('./message.js')
-
!(function() {
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
@@ -10512,17 +10691,16 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
1004: 'The token passed is invalid.'
};
-
- OT.Raptor.Dispatcher = function () {
+ OT.Raptor.Dispatcher = function() {
OT.$.eventing(this, true);
this.callbacks = {};
};
- OT.Raptor.Dispatcher.prototype.registerCallback = function (transactionId, completion) {
+ OT.Raptor.Dispatcher.prototype.registerCallback = function(transactionId, completion) {
this.callbacks[transactionId] = completion;
};
- OT.Raptor.Dispatcher.prototype.triggerCallback = function (transactionId) {
+ OT.Raptor.Dispatcher.prototype.triggerCallback = function(transactionId) {
/*, arg1, arg2, argN-1, argN*/
if (!transactionId) return;
@@ -10542,7 +10720,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
this.emit('close', reason);
};
-
OT.Raptor.Dispatcher.prototype.dispatch = function(rumorMessage) {
// The special casing of STATUS messages is ugly. Need to think about
// how to better integrate this.
@@ -10566,7 +10743,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
OT.debug('OT.Raptor.dispatch ' + message.signature);
OT.debug(rumorMessage.data);
- switch(message.resource) {
+ switch (message.resource) {
case 'session':
this.dispatchSession(message);
break;
@@ -10604,26 +10781,24 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
}
};
- OT.Raptor.Dispatcher.prototype.dispatchSession = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchSession = function(message) {
switch (message.method) {
case 'read':
this.emit('session#read', message.content, message.transactionId);
break;
-
default:
OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented');
}
};
- OT.Raptor.Dispatcher.prototype.dispatchConnection = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchConnection = function(message) {
switch (message.method) {
case 'created':
this.emit('connection#created', message.content);
break;
-
case 'deleted':
this.emit('connection#deleted', message.params.connection, message.reason);
break;
@@ -10633,7 +10808,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
}
};
- OT.Raptor.Dispatcher.prototype.dispatchStream = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchStream = function(message) {
switch (message.method) {
case 'created':
@@ -10645,13 +10820,11 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
message.reason);
break;
-
case 'updated':
this.emit('stream#updated', message.params.stream,
message.content);
break;
-
// The JSEP process
case 'generateoffer':
case 'answer':
@@ -10666,7 +10839,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
}
};
- OT.Raptor.Dispatcher.prototype.dispatchStreamChannel = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchStreamChannel = function(message) {
switch (message.method) {
case 'updated':
this.emit('streamChannel#updated', message.params.stream,
@@ -10694,45 +10867,40 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
// candidate
//
//
- OT.Raptor.Dispatcher.prototype.dispatchJsep = function (method, message) {
+ OT.Raptor.Dispatcher.prototype.dispatchJsep = function(method, message) {
this.emit('jsep#' + method, message.params.stream, message.fromAddress, message);
};
-
- OT.Raptor.Dispatcher.prototype.dispatchSubscriberChannel = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchSubscriberChannel = function(message) {
switch (message.method) {
case 'updated':
this.emit('subscriberChannel#updated', message.params.stream,
message.params.channel, message.content);
break;
-
case 'update': // subscriberId, streamId, content
this.emit('subscriberChannel#update', message.params.subscriber,
message.params.stream, message.content);
break;
-
default:
OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented');
}
};
- OT.Raptor.Dispatcher.prototype.dispatchSubscriber = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchSubscriber = function(message) {
switch (message.method) {
case 'created':
this.emit('subscriber#created', message.params.stream, message.fromAddress,
message.content.id);
break;
-
case 'deleted':
this.dispatchJsep('unsubscribe', message);
this.emit('subscriber#deleted', message.params.stream,
message.fromAddress);
break;
-
// The JSEP process
case 'generateoffer':
case 'answer':
@@ -10742,13 +10910,12 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
this.dispatchJsep(message.method, message);
break;
-
default:
OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented');
}
};
- OT.Raptor.Dispatcher.prototype.dispatchSignal = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchSignal = function(message) {
if (message.method !== 'signal') {
OT.warn('OT.Raptor.dispatch: ' + message.signature + ' is not currently implemented');
return;
@@ -10757,7 +10924,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
message.content.data);
};
- OT.Raptor.Dispatcher.prototype.dispatchArchive = function (message) {
+ OT.Raptor.Dispatcher.prototype.dispatchArchive = function(message) {
switch (message.method) {
case 'created':
this.emit('archive#created', message.content);
@@ -10793,27 +10960,27 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
var connectionId = dict.connectionId ? dict.connectionId : dict.connection.id;
- return new OT.Stream( dict.id,
- dict.name,
- dict.creationTime,
- session.connections.get(connectionId),
- session,
- channel );
+ return new OT.Stream(dict.id,
+ dict.name,
+ dict.creationTime,
+ session.connections.get(connectionId),
+ session,
+ channel);
}
function parseAndAddStreamToSession(dict, session) {
if (session.streams.has(dict.id)) return;
var stream = parseStream(dict, session);
- session.streams.add( stream );
+ session.streams.add(stream);
return stream;
}
function parseArchive(dict) {
- return new OT.Archive( dict.id,
- dict.name,
- dict.status );
+ return new OT.Archive(dict.id,
+ dict.name,
+ dict.status);
}
function parseAndAddArchiveToSession(dict, session) {
@@ -10825,14 +10992,14 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
return archive;
}
- var DelayedEventQueue = function DelayedEventQueue (eventDispatcher) {
+ var DelayedEventQueue = function DelayedEventQueue(eventDispatcher) {
var queue = [];
- this.enqueue = function enqueue (/* arg1, arg2, ..., argN */) {
- queue.push( Array.prototype.slice.call(arguments) );
+ this.enqueue = function enqueue(/* arg1, arg2, ..., argN */) {
+ queue.push(Array.prototype.slice.call(arguments));
};
- this.triggerAll = function triggerAll () {
+ this.triggerAll = function triggerAll() {
var event;
// Array.prototype.shift is actually pretty inefficient for longer Arrays,
@@ -10855,7 +11022,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
// @todo benchmark and see if we should actually care about shift's performance
// for our common queue sizes.
//
- while( (event = queue.shift()) ) {
+ while ((event = queue.shift())) {
eventDispatcher.trigger.apply(eventDispatcher, event);
}
};
@@ -10864,7 +11031,7 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
var DelayedSessionEvents = function(dispatcher) {
var eventQueues = {};
- this.enqueue = function enqueue (/* key, arg1, arg2, ..., argN */) {
+ this.enqueue = function enqueue(/* key, arg1, arg2, ..., argN */) {
var key = arguments[0];
var eventArgs = Array.prototype.slice.call(arguments, 1);
if (!eventQueues[key]) {
@@ -10873,13 +11040,13 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
eventQueues[key].enqueue.apply(eventQueues[key], eventArgs);
};
- this.triggerConnectionCreated = function triggerConnectionCreated (connection) {
+ this.triggerConnectionCreated = function triggerConnectionCreated(connection) {
if (eventQueues['connectionCreated' + connection.id]) {
eventQueues['connectionCreated' + connection.id].triggerAll();
}
};
- this.triggerSessionConnected = function triggerSessionConnected (connections) {
+ this.triggerSessionConnected = function triggerSessionConnected(connections) {
if (eventQueues.sessionConnected) {
eventQueues.sessionConnected.triggerAll();
}
@@ -10912,16 +11079,16 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
return;
}
- connection.destroy( reason );
+ connection.destroy(reason);
});
// This method adds connections to the session both on a connection#created and
// on a session#read. In the case of session#read sessionRead is set to true and
// we include our own connection.
- var addConnection = function (connection, sessionRead) {
+ var addConnection = function(connection, sessionRead) {
connection = OT.Connection.fromHash(connection);
if (sessionRead || session.connection && connection.id !== session.connection.id) {
- session.connections.add( connection );
+ session.connections.add(connection);
delayedSessionEvents.triggerConnectionCreated(connection);
}
@@ -10935,8 +11102,8 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
var payload = {
debug: sessionRead ? 'connection came in session#read' :
'connection came in connection#created',
- streamId : stream.id,
- connectionId : connection.id
+ streamId: stream.id,
+ connectionId: connection.id
};
session.logEvent('streamCreated', 'warning', payload);
}
@@ -10960,11 +11127,11 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
});
OT.$.forEach(content.stream, function(streamParams) {
- state.streams.push( parseAndAddStreamToSession(streamParams, session) );
+ state.streams.push(parseAndAddStreamToSession(streamParams, session));
});
OT.$.forEach(content.archive || content.archives, function(archiveParams) {
- state.archives.push( parseAndAddArchiveToSession(archiveParams, session) );
+ state.archives.push(parseAndAddArchiveToSession(archiveParams, session));
});
session._.subscriberMap = {};
@@ -10992,8 +11159,8 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
unconnectedStreams[stream.id] = stream;
var payload = {
- debug : 'eventOrderError -- streamCreated event before connectionCreated',
- streamId : stream.id,
+ debug: 'eventOrderError -- streamCreated event before connectionCreated',
+ streamId: stream.id
};
session.logEvent('streamCreated', 'warning', payload);
}
@@ -11072,7 +11239,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
if (subscriber) actors.push(subscriber);
break;
-
// Messages for Publishers
case 'answer':
case 'pranswer':
@@ -11081,7 +11247,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
actors = OT.publishers.where({streamId: streamId});
break;
-
// Messages for Publishers and Subscribers
case 'candidate':
// send to whichever of your publisher or subscribers are
@@ -11090,7 +11255,6 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
.concat(OT.subscribers.where({streamId: streamId}));
break;
-
default:
OT.warn('OT.Raptor.dispatch: jsep#' + method +
' is not currently implemented');
@@ -11103,14 +11267,14 @@ OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type,
// until we find the actor that the message relates to this stream, and then
// we grab the session from it.
fromConnection = actors[0].session.connections.get(fromAddress);
- if(!fromConnection && fromAddress.match(/^symphony\./)) {
+ if (!fromConnection && fromAddress.match(/^symphony\./)) {
fromConnection = OT.Connection.fromHash({
id: fromAddress,
creationTime: Math.floor(OT.$.now())
});
actors[0].session.connections.add(fromConnection);
- } else if(!fromConnection) {
+ } else if (!fromConnection) {
OT.warn('OT.Raptor.dispatch: Messsage comes from a connection (' +
fromAddress + ') that we do not know about. The message was ignored.');
return;
@@ -11374,6 +11538,9 @@ OT.httpTest = httpTest;
/* exported SDPHelpers */
+var START_MEDIA_SSRC = 10000,
+ START_RTX_SSRC = 20000;
+
// Here are the structure of the rtpmap attribute and the media line, most of the
// complex Regular Expressions in this code are matching against one of these two
// formats:
@@ -11384,108 +11551,329 @@ OT.httpTest = httpTest;
// * https://tools.ietf.org/html/rfc4566
// * http://en.wikipedia.org/wiki/Session_Description_Protocol
//
-var SDPHelpers = {
- // Search through sdpLines to find the Media Line of type +mediaType+.
- getMLineIndex: function getMLineIndex(sdpLines, mediaType) {
- var targetMLine = 'm=' + mediaType;
+var SDPHelpers = {};
- // Find the index of the media line for +type+
- return OT.$.findIndex(sdpLines, function(line) {
- if (line.indexOf(targetMLine) !== -1) {
- return true;
- }
+// Search through sdpLines to find the Media Line of type +mediaType+.
+SDPHelpers.getMLineIndex = function getMLineIndex(sdpLines, mediaType) {
+ var targetMLine = 'm=' + mediaType;
- return false;
- });
- },
-
- // Extract the payload types for a give Media Line.
- //
- getMLinePayloadTypes: function getMLinePayloadTypes (mediaLine, mediaType) {
- var mLineSelector = new RegExp('^m=' + mediaType +
- ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i');
-
- // Get all payload types that the line supports
- var payloadTypes = mediaLine.match(mLineSelector);
- if (!payloadTypes || payloadTypes.length < 2) {
- // Error, invalid M line?
- return [];
+ // Find the index of the media line for +type+
+ return OT.$.findIndex(sdpLines, function(line) {
+ if (line.indexOf(targetMLine) !== -1) {
+ return true;
}
- return OT.$.trim(payloadTypes[2]).split(' ');
- },
+ return false;
+ });
+};
- removeTypesFromMLine: function removeTypesFromMLine (mediaLine, payloadTypes) {
- return OT.$.trim(
- mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig') , ' ')
- .replace(/\s+/g, ' ') );
- },
+// Grab a M line of a particular +mediaType+ from sdpLines.
+SDPHelpers.getMLine = function getMLine(sdpLines, mediaType) {
+ var mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType);
+ return mLineIndex > -1 ? sdpLines[mLineIndex] : void 0;
+};
-
- // Remove all references to a particular encodingName from a particular media type
- //
- removeMediaEncoding: function removeMediaEncoding (sdp, mediaType, encodingName) {
- var sdpLines = sdp.split('\r\n'),
- mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType),
- mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0,
- typesToRemove = [],
- payloadTypes,
- match;
-
- if (mLineIndex === -1) {
- // Error, missing M line
- return sdpLines.join('\r\n');
- }
-
- // Get all payload types that the line supports
+SDPHelpers.hasMLinePayloadType = function hasMLinePayloadType(sdpLines, mediaType, payloadType) {
+ var mLine = SDPHelpers.getMLine(sdpLines, mediaType),
payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType);
- if (payloadTypes.length === 0) {
- // Error, invalid M line?
- return sdpLines.join('\r\n');
+
+ return OT.$.arrayIndexOf(payloadTypes, payloadType) > -1;
+};
+
+// Extract the payload types for a give Media Line.
+//
+SDPHelpers.getMLinePayloadTypes = function getMLinePayloadTypes(mediaLine, mediaType) {
+ var mLineSelector = new RegExp('^m=' + mediaType +
+ ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i');
+
+ // Get all payload types that the line supports
+ var payloadTypes = mediaLine.match(mLineSelector);
+ if (!payloadTypes || payloadTypes.length < 2) {
+ // Error, invalid M line?
+ return [];
+ }
+
+ return OT.$.trim(payloadTypes[2]).split(' ');
+};
+
+SDPHelpers.removeTypesFromMLine = function removeTypesFromMLine(mediaLine, payloadTypes) {
+ return OT.$.trim(
+ mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig'), ' ')
+ .replace(/\s+/g, ' '));
+};
+
+// Remove all references to a particular encodingName from a particular media type
+//
+SDPHelpers.removeMediaEncoding = function removeMediaEncoding(sdp, mediaType, encodingName) {
+ var sdpLines = sdp.split('\r\n'),
+ mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType),
+ mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0,
+ typesToRemove = [],
+ payloadTypes,
+ match;
+
+ if (mLineIndex === -1) {
+ // Error, missing M line
+ return sdpLines.join('\r\n');
+ }
+
+ // Get all payload types that the line supports
+ payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType);
+ if (payloadTypes.length === 0) {
+ // Error, invalid M line?
+ return sdpLines.join('\r\n');
+ }
+
+ // Find the location of all the rtpmap lines that relate to +encodingName+
+ // and any of the supported payload types
+ var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' +
+ encodingName + '\\/\\d+', 'i');
+
+ sdpLines = OT.$.filter(sdpLines, function(line, index) {
+ match = line.match(matcher);
+ if (match === null) return true;
+
+ typesToRemove.push(match[1]);
+
+ if (index < mLineIndex) {
+ // This removal changed the index of the mline, track it
+ mLineIndex--;
}
- // Find the location of all the rtpmap lines that relate to +encodingName+
- // and any of the supported payload types
- var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' +
- encodingName + '\\/\\d+', 'i');
+ // remove this one
+ return false;
+ });
- sdpLines = OT.$.filter(sdpLines, function(line, index) {
- match = line.match(matcher);
- if (match === null) return true;
+ if (typesToRemove.length > 0 && mLineIndex > -1) {
+ // Remove all the payload types and we've removed from the media line
+ sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove);
+ }
- typesToRemove.push(match[1]);
+ return sdpLines.join('\r\n');
+};
- if (index < mLineIndex) {
- // This removal changed the index of the mline, track it
- mLineIndex--;
+// Removes all Confort Noise from +sdp+.
+//
+// See https://jira.tokbox.com/browse/OPENTOK-7176
+//
+SDPHelpers.removeComfortNoise = function removeComfortNoise(sdp) {
+ return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN');
+};
+
+SDPHelpers.removeVideoCodec = function removeVideoCodec(sdp, codec) {
+ return SDPHelpers.removeMediaEncoding(sdp, 'video', codec);
+};
+
+// Used to identify whether Video media (for a given set of SDP) supports
+// retransmissions.
+//
+// The algorithm to do could be summarised as:
+//
+// IF ssrc-group:FID exists AND IT HAS AT LEAST TWO IDS THEN
+// we are using RTX
+// ELSE IF "a=rtpmap: (\\d+):rtxPayloadId(/\\d+)? rtx/90000"
+// AND SDPHelpers.hasMLinePayloadType(sdpLines, 'Video', rtxPayloadId)
+// we are using RTX
+// ELSE
+// we are not using RTX
+//
+// The ELSE IF clause basically covers the case where ssrc-group:FID
+// is probably malformed or missing. In that case we verify whether
+// we want RTX by looking at whether it's mentioned in the video
+// media line instead.
+//
+var isUsingRTX = function isUsingRTX(sdpLines, videoAttrs) {
+ var groupFID = videoAttrs.filterByName('ssrc-group:FID'),
+ missingFID = groupFID.length === 0;
+
+ if (groupFID.length > 1) {
+ // @todo Wut?
+ throw new Error('WTF Simulcast?!');
+ }
+
+ if (!missingFID) groupFID = groupFID[0].value.split(' ');
+ else groupFID = [];
+
+ switch (groupFID.length) {
+ case 0:
+ case 1:
+ // possibly no RTX, double check for the RTX payload type and that
+ // the Video Media line contains that payload type
+ //
+ // Details: Look for a rtpmap line for rtx/90000
+ // If there is one, grab the payload ID for rtx
+ // Look to see if that payload ID is listed under the payload types for the m=Video line
+ // If it is: RTX
+ // else: No RTX for you
+
+ var rtxAttr = videoAttrs.find(function(attr) {
+ return attr.name.indexOf('rtpmap:') === 0 &&
+ attr.value.indexOf('rtx/90000') > -1;
+ });
+
+ if (!rtxAttr) {
+ return false;
}
- // remove this one
- return false;
- });
+ var rtxPayloadId = rtxAttr.name.split(':')[1];
+ if (rtxPayloadId.indexOf('/') > -1) rtxPayloadId = rtxPayloadId.split('/')[0];
+ return SDPHelpers.hasMLinePayloadType(sdpLines, 'video', rtxPayloadId);
- if (typesToRemove.length > 0 && mLineIndex > -1) {
- // Remove all the payload types and we've removed from the media line
- sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove);
- }
-
- return sdpLines.join('\r\n');
- },
-
- // Removes all Confort Noise from +sdp+.
- //
- // See https://jira.tokbox.com/browse/OPENTOK-7176
- //
- removeComfortNoise: function removeComfortNoise (sdp) {
- return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN');
- },
-
- removeVideoCodec: function removeVideoCodec (sdp, codec) {
- return SDPHelpers.removeMediaEncoding(sdp, 'video', codec);
+ default:
+ // two or more: definitely RTX
+ return true;
}
};
+// This returns an Array, which is decorated with several
+// SDP specific helper methods.
+//
+SDPHelpers.getAttributesForMediaType = function getAttributesForMediaType(sdpLines, mediaType) {
+ var mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType),
+ matchOtherMLines = new RegExp('m=(^' + mediaType + ') ', 'i'),
+ matchSSRCLines = new RegExp('a=ssrc:\\d+ .*', 'i'),
+ matchSSRCGroup = new RegExp('a=ssrc-group:FID (\\d+).*?', 'i'),
+ matchAttrLine = new RegExp('a=([a-z0-9:/-]+) (.*)', 'i'),
+ ssrcStartIndex,
+ ssrcEndIndex,
+ attrs = [],
+ regResult,
+ ssrc, ssrcGroup,
+ msidMatch, msid;
+ for (var i = mLineIndex + 1; i < sdpLines.length; i++) {
+ if (matchOtherMLines.test(sdpLines[i])) {
+ break;
+ }
+
+ // Get the ssrc
+ ssrcGroup = sdpLines[i].match(matchSSRCGroup);
+ if (ssrcGroup) {
+ ssrcStartIndex = i;
+ ssrc = ssrcGroup[1];
+ }
+
+ // Get the msid
+ msidMatch = sdpLines[i].match('a=ssrc:' + ssrc + ' msid:(.+)');
+ if (msidMatch) {
+ msid = msidMatch[1];
+ }
+
+ // find where the ssrc lines end
+ var isSSRCLine = matchSSRCLines.test(sdpLines[i]);
+ if (ssrcStartIndex !== undefined && ssrcEndIndex === undefined && !isSSRCLine ||
+ i === sdpLines.length - 1) {
+ ssrcEndIndex = i;
+ }
+
+ regResult = matchAttrLine.exec(sdpLines[i]);
+ if (regResult && regResult.length === 3) {
+ attrs.push({
+ name: regResult[1],
+ value: regResult[2]
+ });
+ }
+ }
+
+ /// The next section decorates the attributes array
+ /// with some useful helpers.
+
+ // Store references to the start and end indices
+ // of the media section for this mediaType
+ attrs.ssrcStartIndex = ssrcStartIndex;
+ attrs.ssrcEndIndex = ssrcEndIndex;
+ attrs.msid = msid;
+
+ // Add filter support is
+ if (!Array.prototype.filter) {
+ attrs.filter = OT.$.bind(OT.$.filter, OT.$, attrs);
+ }
+
+ if (!Array.prototype.find) {
+ attrs.find = OT.$.bind(OT.$.find, OT.$, attrs);
+ }
+
+ attrs.isUsingRTX = OT.$.bind(isUsingRTX, null, sdpLines, attrs);
+
+ attrs.filterByName = function(name) {
+ return this.filter(function(attr) {
+ return attr.name === name;
+ });
+ };
+
+ return attrs;
+};
+
+// Modifies +sdp+ to enable Simulcast for +numberOfStreams+.
+//
+// Ok, here's the plan:
+// - add the 'a=ssrc-group:SIM' line, it will have numberOfStreams ssrcs
+// - if RTX then add one 'a=ssrc-group:FID', we need to add numberOfStreams lines
+// - add numberOfStreams 'a=ssrc:...' lines for the media ssrc
+// - if RTX then add numberOfStreams 'a=ssrc:...' lines for the RTX ssrc
+//
+// Re: media and rtx ssrcs:
+// We just generate these. The Mantis folk would like us to use sequential numbers
+// here for ease of debugging. We can use the same starting number each time as well.
+// We should confirm with Oscar/Jose that whether we need to verify that the numbers
+// that we choose don't clash with any other ones in the SDP.
+//
+// I think we do need to check but I can't remember.
+//
+// Re: The format of the 'a=ssrc:' lines
+// Just use the following pattern:
+// a=ssrc: cname:localCname
+// a=ssrc: msid:
+//
+// It doesn't matter that they are all the same and are static.
+//
+//
+SDPHelpers.enableSimulcast = function enableSimulcast(sdp, numberOfStreams) {
+ var sdpLines = sdp.split('\r\n'),
+ videoAttrs = SDPHelpers.getAttributesForMediaType(sdpLines, 'video'),
+ usingRTX = videoAttrs.isUsingRTX(),
+ mediaSSRC = [],
+ rtxSSRC = [],
+ linesToAdd,
+ i;
+
+ // generate new media (and rtx if needed) ssrcs
+ for (i = 0; i < numberOfStreams; ++i) {
+ mediaSSRC.push(START_MEDIA_SSRC + i);
+ if (usingRTX) rtxSSRC.push(START_RTX_SSRC + i);
+ }
+
+ linesToAdd = [
+ 'a=ssrc-group:SIM ' + mediaSSRC.join(' ')
+ ];
+
+ if (usingRTX) {
+ for (i = 0; i < numberOfStreams; ++i) {
+ linesToAdd.push('a=ssrc-group:FID ' + mediaSSRC[i] + ' ' + rtxSSRC[i]);
+ }
+ }
+
+ for (i = 0; i < numberOfStreams; ++i) {
+ linesToAdd.push('a=ssrc:' + mediaSSRC[i] + ' cname:localCname',
+ 'a=ssrc:' + mediaSSRC[i] + ' msid:' + videoAttrs.msid);
+
+ }
+
+ if (usingRTX) {
+ for (i = 0; i < numberOfStreams; ++i) {
+ linesToAdd.push('a=ssrc:' + rtxSSRC[i] + ' cname:localCname',
+ 'a=ssrc:' + rtxSSRC[i] + ' msid:' + videoAttrs.msid);
+ }
+ }
+
+ // Replace the previous video ssrc section with our new video ssrc section by
+ // deleting the old ssrcs section and inserting the new lines
+ linesToAdd.unshift(videoAttrs.ssrcStartIndex, videoAttrs.ssrcEndIndex -
+ videoAttrs.ssrcStartIndex);
+ sdpLines.splice.apply(sdpLines, linesToAdd);
+
+ return sdpLines.join('\r\n');
+};
// tb_require('../../helpers/helpers.js')
@@ -11556,9 +11944,9 @@ OT.getStatsHelpers = getStatsHelpers;
function getStatsAdapter() {
///
-// Get Stats using the older API. Used by all current versions
-// of Chrome.
-//
+ // Get Stats using the older API. Used by all current versions
+ // of Chrome.
+ //
function getStatsOldAPI(peerConnection, completion) {
peerConnection.getStats(function(rtcStatsReport) {
@@ -11583,9 +11971,9 @@ function getStatsAdapter() {
});
}
-///
-// Get Stats using the newer API.
-//
+ ///
+ // Get Stats using the newer API.
+ //
function getStatsNewAPI(peerConnection, completion) {
peerConnection.getStats(null, function(rtcStatsReport) {
@@ -11632,13 +12020,11 @@ function webrtcTest(config) {
NativeRTCSessionDescription = (window.mozRTCSessionDescription ||
window.RTCSessionDescription);
NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate);
- }
- else {
+ } else {
NativeRTCSessionDescription = OTPlugin.RTCSessionDescription;
NativeRTCIceCandidate = OTPlugin.RTCIceCandidate;
}
-
function isCandidateRelay(candidate) {
return candidate.candidate.indexOf('relay') !== -1;
}
@@ -11815,7 +12201,7 @@ function webrtcTest(config) {
if (stat.hasOwnProperty('bytesReceived')) {
_statsSamples.videoBytesReceived = parseInt(stat.bytesReceived, 10);
}
- } else if(OT.getStatsHelpers.isAudioStat(stat)) {
+ } else if (OT.getStatsHelpers.isAudioStat(stat)) {
if (stat.hasOwnProperty('bytesReceived')) {
_statsSamples.audioBytesReceived = parseInt(stat.bytesReceived, 10);
}
@@ -11984,7 +12370,6 @@ OT.Chrome = function(properties) {
}
};
-
// Adds the widget to the chrome and to the DOM. Also creates a accessor
// property for it on the chrome.
//
@@ -12000,7 +12385,7 @@ OT.Chrome = function(properties) {
// chrome.foo.setDisplayMode('on');
//
this.set = function(widgetName, widget) {
- if (typeof(widgetName) === 'string' && widget) {
+ if (typeof widgetName === 'string' && widget) {
_set.call(this, widgetName, widget);
} else {
@@ -12015,7 +12400,6 @@ OT.Chrome = function(properties) {
};
-
// tb_require('../../../helpers/helpers.js')
// tb_require('../chrome.js')
@@ -12107,7 +12491,6 @@ OT.Chrome.Behaviour.Widget = function(widget, options) {
}, 2000);
}
-
// add the widget to the parent
parent.appendChild(this.domElement);
@@ -12123,39 +12506,38 @@ OT.Chrome.Behaviour.Widget = function(widget, options) {
/* global OT */
OT.Chrome.VideoDisabledIndicator = function(options) {
- var _videoDisabled = false,
- _warning = false,
+ var videoDisabled = false,
+ warning = false,
updateClasses;
- updateClasses = OT.$.bind(function(domElement) {
- if (_videoDisabled) {
- OT.$.addClass(domElement, 'OT_video-disabled');
- } else {
- OT.$.removeClass(domElement, 'OT_video-disabled');
+ updateClasses = OT.$.bind(function(element) {
+ var shouldDisplay = ['auto', 'on'].indexOf(this.getDisplayMode()) > -1;
+
+ OT.$.removeClass(element, 'OT_video-disabled OT_video-disabled-warning OT_active');
+
+ if (!shouldDisplay) {
+ return;
}
- if(_warning) {
- OT.$.addClass(domElement, 'OT_video-disabled-warning');
- } else {
- OT.$.removeClass(domElement, 'OT_video-disabled-warning');
- }
- if ((_videoDisabled || _warning) &&
- (this.getDisplayMode() === 'auto' || this.getDisplayMode() === 'on')) {
- OT.$.addClass(domElement, 'OT_active');
- } else {
- OT.$.removeClass(domElement, 'OT_active');
+
+ if (videoDisabled) {
+ OT.$.addClass(element, 'OT_video-disabled');
+ } else if (warning) {
+ OT.$.addClass(element, 'OT_video-disabled-warning');
}
+
+ OT.$.addClass(element, 'OT_active');
}, this);
this.disableVideo = function(value) {
- _videoDisabled = value;
- if(value === true) {
- _warning = false;
+ videoDisabled = value;
+ if (value === true) {
+ warning = false;
}
updateClasses(this.domElement);
};
this.setWarning = function(value) {
- _warning = value;
+ warning = value;
updateClasses(this.domElement);
};
@@ -12320,13 +12702,13 @@ OT.Chrome.BackingBar = function(options) {
_muteMode = options.muteMode;
function getDisplayMode() {
- if(_nameMode === 'on' || _muteMode === 'on') {
+ if (_nameMode === 'on' || _muteMode === 'on') {
return 'on';
- } else if(_nameMode === 'mini' || _muteMode === 'mini') {
+ } else if (_nameMode === 'mini' || _muteMode === 'mini') {
return 'mini';
- } else if(_nameMode === 'mini-auto' || _muteMode === 'mini-auto') {
+ } else if (_nameMode === 'mini-auto' || _muteMode === 'mini-auto') {
return 'mini-auto';
- } else if(_nameMode === 'auto' || _muteMode === 'auto') {
+ } else if (_nameMode === 'auto' || _muteMode === 'auto') {
return 'auto';
} else {
return 'off';
@@ -12355,7 +12737,6 @@ OT.Chrome.BackingBar = function(options) {
};
-
// tb_require('../../helpers/helpers.js')
// tb_require('./behaviour/widget.js')
@@ -12363,7 +12744,6 @@ OT.Chrome.BackingBar = function(options) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
OT.Chrome.AudioLevelMeter = function(options) {
var widget = this,
@@ -12432,7 +12812,6 @@ OT.Chrome.AudioLevelMeter = function(options) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
// Archving Chrome Widget
//
// mode (String)
@@ -12465,12 +12844,12 @@ OT.Chrome.Archiving = function(options) {
};
renderStage = OT.$.bind(function() {
- if(renderStageDelayedAction) {
+ if (renderStageDelayedAction) {
clearTimeout(renderStageDelayedAction);
renderStageDelayedAction = null;
}
- if(_archiving) {
+ if (_archiving) {
OT.$.addClass(_light, 'OT_active');
} else {
OT.$.removeClass(_light, 'OT_active');
@@ -12478,7 +12857,7 @@ OT.Chrome.Archiving = function(options) {
OT.$.removeClass(this.domElement, 'OT_archiving-' + (!_archiving ? 'on' : 'off'));
OT.$.addClass(this.domElement, 'OT_archiving-' + (_archiving ? 'on' : 'off'));
- if(options.show && _archiving) {
+ if (options.show && _archiving) {
renderText(_archivingStarted);
OT.$.addClass(_text, 'OT_mode-on');
OT.$.removeClass(_text, 'OT_mode-auto');
@@ -12487,7 +12866,7 @@ OT.Chrome.Archiving = function(options) {
OT.$.addClass(_text, 'OT_mode-auto');
OT.$.removeClass(_text, 'OT_mode-on');
}, 5000);
- } else if(options.show && !_initialState) {
+ } else if (options.show && !_initialState) {
OT.$.addClass(_text, 'OT_mode-on');
OT.$.removeClass(_text, 'OT_mode-auto');
this.setDisplayMode('on');
@@ -12526,7 +12905,7 @@ OT.Chrome.Archiving = function(options) {
this.setShowArchiveStatus = OT.$.bind(function(show) {
options.show = show;
- if(this.domElement) {
+ if (this.domElement) {
renderStage.call(this);
}
}, this);
@@ -12534,7 +12913,7 @@ OT.Chrome.Archiving = function(options) {
this.setArchiving = OT.$.bind(function(status) {
_archiving = status;
_initialState = false;
- if(this.domElement) {
+ if (this.domElement) {
renderStage.call(this);
}
}, this);
@@ -12550,7 +12929,7 @@ OT.Chrome.Archiving = function(options) {
// Web OT Helpers
!(function(window) {
// guard for Node.js
- if (window && typeof(navigator) !== 'undefined') {
+ if (window && typeof navigator !== 'undefined') {
var NativeRTCPeerConnection = (window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection);
@@ -12619,7 +12998,7 @@ OT.Chrome.Archiving = function(options) {
// all the good browsers down to IE's level than bootstrap it up.
if (typeof window.MediaStreamTrack !== 'undefined') {
if (!window.MediaStreamTrack.prototype.setEnabled) {
- window.MediaStreamTrack.prototype.setEnabled = function (enabled) {
+ window.MediaStreamTrack.prototype.setEnabled = function(enabled) {
this.enabled = OT.$.castToBoolean(enabled);
};
}
@@ -12629,17 +13008,16 @@ OT.Chrome.Archiving = function(options) {
window.URL = window.webkitURL;
}
- OT.$.createPeerConnection = function (config, options, publishersWebRtcStream, completion) {
+ OT.$.createPeerConnection = function(config, options, publishersWebRtcStream, completion) {
if (OTPlugin.isInstalled()) {
OTPlugin.initPeerConnection(config, options,
publishersWebRtcStream, completion);
- }
- else {
+ } else {
var pc;
try {
pc = new NativeRTCPeerConnection(config, options);
- } catch(e) {
+ } catch (e) {
completion(e.message);
return;
}
@@ -12676,7 +13054,7 @@ OT.Chrome.Archiving = function(options) {
// If there are no issues, the +success+ callback will be executed on completion.
// Errors during any step will result in the +failure+ callback being executed.
//
-var subscribeProcessor = function(peerConnection, success, failure) {
+var subscribeProcessor = function(peerConnection, numberOfSimulcastStreams, success, failure) {
var generateErrorCallback,
setLocalDescription;
@@ -12693,6 +13071,9 @@ var subscribeProcessor = function(peerConnection, success, failure) {
offer.sdp = SDPHelpers.removeComfortNoise(offer.sdp);
offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'ulpfec');
offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'red');
+ if (numberOfSimulcastStreams > 1) {
+ offer.sdp = SDPHelpers.enableSimulcast(offer.sdp, numberOfSimulcastStreams);
+ }
peerConnection.setLocalDescription(
offer,
@@ -12781,7 +13162,7 @@ var offerProcessor = function(peerConnection, offer, success, failure) {
if (offer.sdp.indexOf('a=rtcp-fb') === -1) {
var rtcpFbLine = 'a=rtcp-fb:* ccm fir\r\na=rtcp-fb:* nack ';
- offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n'+rtcpFbLine);
+ offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n' + rtcpFbLine);
}
peerConnection.setRemoteDescription(
@@ -12795,6 +13176,7 @@ var offerProcessor = function(peerConnection, offer, success, failure) {
);
};
+
// tb_require('../../helpers/helpers.js')
// tb_require('../../helpers/lib/web_rtc.js')
@@ -12805,8 +13187,7 @@ var NativeRTCIceCandidate;
if (!OTPlugin.isInstalled()) {
NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate);
-}
-else {
+} else {
NativeRTCIceCandidate = OTPlugin.RTCIceCandidate;
}
@@ -12844,24 +13225,24 @@ var IceCandidateProcessor = function() {
};
this.processPending = function() {
- while(_pendingIceCandidates.length) {
+ while (_pendingIceCandidates.length) {
_peerConnection.addIceCandidate(_pendingIceCandidates.shift());
}
};
};
+
// tb_require('../../helpers/helpers.js')
// tb_require('../../helpers/lib/web_rtc.js')
/* exported DataChannel */
-
// Wraps up a native RTCDataChannelEvent object for the message event. This is
// so we never accidentally leak the native DataChannel.
//
// @constructor
// @private
//
-var DataChannelMessageEvent = function DataChanneMessageEvent (event) {
+var DataChannelMessageEvent = function DataChanneMessageEvent(event) {
this.data = event.data;
this.source = event.source;
this.lastEventId = event.lastEventId;
@@ -12872,7 +13253,6 @@ var DataChannelMessageEvent = function DataChanneMessageEvent (event) {
this.path = event.path;
};
-
// DataChannel is a wrapper of the native browser RTCDataChannel object.
//
// It exposes an interface that is very similar to the native one except
@@ -12912,54 +13292,61 @@ var DataChannelMessageEvent = function DataChanneMessageEvent (event) {
// @constructor
// @private
//
-var DataChannel = function DataChannel (dataChannel) {
+var DataChannel = function DataChannel(dataChannel) {
var api = {};
/// Private Data
var bufferedMessages = [];
+ var qosData = {
+ sentMessages: 0,
+ recvMessages: 0
+ };
/// Private API
- var bufferMessage = function bufferMessage (data) {
+ var bufferMessage = function bufferMessage(data) {
bufferedMessages.push(data);
return api;
},
- sendMessage = function sendMessage (data) {
+ sendMessage = function sendMessage(data) {
dataChannel.send(data);
+ qosData.sentMessages++;
+
return api;
},
- flushBufferedMessages = function flushBufferedMessages () {
+ flushBufferedMessages = function flushBufferedMessages() {
var data;
- while ( (data = bufferedMessages.shift()) ) {
+ while ((data = bufferedMessages.shift())) {
api.send(data);
}
},
- onOpen = function onOpen () {
+ onOpen = function onOpen() {
api.send = sendMessage;
flushBufferedMessages();
},
- onClose = function onClose (event) {
+ onClose = function onClose(event) {
api.send = bufferMessage;
api.trigger('close', event);
},
- onError = function onError (event) {
+ onError = function onError(event) {
OT.error('Data Channel Error:', event);
},
- onMessage = function onMessage (domEvent) {
+ onMessage = function onMessage(domEvent) {
var event = new DataChannelMessageEvent(domEvent);
+ qosData.recvMessages++;
+
api.trigger('message', event);
};
-
/// Public API
OT.$.eventing(api, true);
@@ -12973,11 +13360,12 @@ var DataChannel = function DataChannel (dataChannel) {
api.ordered = dataChannel.ordered;
api.protocol = dataChannel.protocol;
api._channel = dataChannel;
- api.close = function () {
+
+ api.close = function() {
dataChannel.close();
};
- api.equals = function (label, options) {
+ api.equals = function(label, options) {
if (api.label !== label) return false;
for (var key in options) {
@@ -12991,11 +13379,14 @@ var DataChannel = function DataChannel (dataChannel) {
return true;
};
+ api.getQosData = function() {
+ return qosData;
+ };
+
// Send initially just buffers all messages until
// the channel is open.
api.send = bufferMessage;
-
/// Init
dataChannel.addEventListener('open', onOpen, false);
dataChannel.addEventListener('close', onClose, false);
@@ -13004,6 +13395,7 @@ var DataChannel = function DataChannel (dataChannel) {
return api;
};
+
// tb_require('../../helpers/helpers.js')
// tb_require('../../helpers/lib/web_rtc.js')
// tb_require('./data_channel.js')
@@ -13018,21 +13410,25 @@ var DataChannel = function DataChannel (dataChannel) {
// @constructor
// @private
//
-var PeerConnectionChannels = function PeerConnectionChannels (pc) {
+var PeerConnectionChannels = function PeerConnectionChannels(pc) {
/// Private Data
var channels = [],
api = {};
+ var lastQos = {
+ sentMessages: 0,
+ recvMessages: 0
+ };
/// Private API
- var remove = function remove (channel) {
+ var remove = function remove(channel) {
OT.$.filter(channels, function(c) {
return channel !== c;
});
};
- var add = function add (nativeChannel) {
+ var add = function add(nativeChannel) {
var channel = new DataChannel(nativeChannel);
channels.push(channel);
@@ -13043,14 +13439,13 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) {
return channel;
};
-
/// Public API
- api.add = function (label, options) {
+ api.add = function(label, options) {
return add(pc.createDataChannel(label, options));
};
- api.addMany = function (newChannels) {
+ api.addMany = function(newChannels) {
for (var label in newChannels) {
if (newChannels.hasOwnProperty(label)) {
api.add(label, newChannels[label]);
@@ -13058,13 +13453,13 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) {
}
};
- api.get = function (label, options) {
+ api.get = function(label, options) {
return OT.$.find(channels, function(channel) {
return channel.equals(label, options);
});
};
- api.getOrAdd = function (label, options) {
+ api.getOrAdd = function(label, options) {
var channel = api.get(label, options);
if (!channel) {
channel = api.add(label, options);
@@ -13073,7 +13468,34 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) {
return channel;
};
- api.destroy = function () {
+ api.getQosData = function() {
+ var qosData = {
+ sentMessages: 0,
+ recvMessages: 0
+ };
+
+ OT.$.forEach(channels, function(channel) {
+ qosData.sentMessages += channel.getQosData().sentMessages;
+ qosData.recvMessages += channel.getQosData().recvMessages;
+ });
+
+ return qosData;
+ };
+
+ api.sampleQos = function() {
+ var newQos = api.getQosData();
+
+ var sampleQos = {
+ sentMessages: newQos.sentMessages - lastQos.sentMessages,
+ recvMessages: newQos.recvMessages - lastQos.recvMessages
+ };
+
+ lastQos = newQos;
+
+ return sampleQos;
+ };
+
+ api.destroy = function() {
OT.$.forEach(channels, function(channel) {
channel.close();
});
@@ -13081,7 +13503,6 @@ var PeerConnectionChannels = function PeerConnectionChannels (pc) {
channels = [];
};
-
/// Init
pc.addEventListener('datachannel', function(event) {
@@ -13129,7 +13550,7 @@ var connectionStateLogger = function(pc) {
var trackState = function() {
var now = OT.$.now(),
- lastState = states[states.length-1],
+ lastState = states[states.length - 1],
state = {delta: finishTime ? now - finishTime : 0};
if (!lastState || lastState.iceConnection !== pc.iceConnectionState) {
@@ -13153,7 +13574,7 @@ var connectionStateLogger = function(pc) {
pc.addEventListener('icegatheringstatechange', trackState, false);
return {
- stop: function () {
+ stop: function() {
pc.removeEventListener('iceconnectionstatechange', trackState, false);
pc.removeEventListener('signalingstatechange', trackState, false);
pc.removeEventListener('icegatheringstatechange', trackState, false);
@@ -13196,12 +13617,10 @@ if (!OTPlugin.isInstalled()) {
// order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless
NativeRTCSessionDescription = (window.mozRTCSessionDescription ||
window.RTCSessionDescription);
-}
-else {
+} else {
NativeRTCSessionDescription = OTPlugin.RTCSessionDescription;
}
-
// Helper function to forward Ice Candidates via +messageDelegate+
var iceCandidateForwarder = function(messageDelegate) {
return function(event) {
@@ -13239,7 +13658,6 @@ OT.PeerConnection = function(config) {
_messageDelegates = [],
api = {};
-
OT.$.eventing(api);
// if ice servers doesn't exist Firefox will throw an exception. Chrome
@@ -13271,7 +13689,7 @@ OT.PeerConnection = function(config) {
// of OTPlugin, it's not used for vanilla WebRTC. Hopefully this can
// be tidied up later.
//
- createPeerConnection = function (completion, localWebRtcStream) {
+ createPeerConnection = function(completion, localWebRtcStream) {
if (_peerConnection) {
completion.call(null, null, _peerConnection);
return;
@@ -13296,12 +13714,15 @@ OT.PeerConnection = function(config) {
]
};
-
OT.debug('Creating peer connection config "' + JSON.stringify(config) + '".');
if (!config.iceServers || config.iceServers.length === 0) {
// This should never happen unless something is misconfigured
OT.error('No ice servers present');
+ OT.analytics.logEvent({
+ action: 'Error',
+ variation: 'noIceServers'
+ });
}
OT.$.createPeerConnection(config, pcConstraints, localWebRtcStream,
@@ -13337,7 +13758,7 @@ OT.PeerConnection = function(config) {
if (_peerConnection.oniceconnectionstatechange !== void 0) {
var failedStateTimer;
- _peerConnection.addEventListener('iceconnectionstatechange', function (event) {
+ _peerConnection.addEventListener('iceconnectionstatechange', function(event) {
if (event.target.iceConnectionState === 'failed') {
if (failedStateTimer) {
clearTimeout(failedStateTimer);
@@ -13346,12 +13767,18 @@ OT.PeerConnection = function(config) {
// We wait 5 seconds and make sure that it's still in the failed state
// before we trigger the error. This is because we sometimes see
// 'failed' and then 'connected' afterwards.
- failedStateTimer = setTimeout(function () {
+ failedStateTimer = setTimeout(function() {
if (event.target.iceConnectionState === 'failed') {
triggerError('The stream was unable to connect due to a network error.' +
' Make sure your connection isn\'t blocked by a firewall.', 'ICEWorkflow');
}
}, 5000);
+ } else if (event.target.iceConnectionState === 'disconnected') {
+ OT.analytics.logEvent({
+ action: 'attachEventsToPeerConnection',
+ variation: 'iceconnectionstatechange',
+ payload: 'disconnected'
+ });
}
}, false);
}
@@ -13359,7 +13786,7 @@ OT.PeerConnection = function(config) {
triggerPeerConnectionCompletion(null);
},
- triggerPeerConnectionCompletion = function () {
+ triggerPeerConnectionCompletion = function() {
while (_peerConnectionCompletionHandlers.length) {
_peerConnectionCompletionHandlers.shift().call(null);
}
@@ -13394,7 +13821,7 @@ OT.PeerConnection = function(config) {
_state = newState;
OT.debug('PeerConnection.stateChange: ' + _state);
- switch(_state) {
+ switch (_state) {
case 'closed':
tearDownPeerConnection();
break;
@@ -13403,6 +13830,7 @@ OT.PeerConnection = function(config) {
},
qosCallback = function(parsedStats) {
+ parsedStats.dataChannels = _channels.sampleQos();
api.trigger('qos', parsedStats);
},
@@ -13435,14 +13863,12 @@ OT.PeerConnection = function(config) {
// ICE Negotiation messages
-
// Relays a SDP payload (+sdp+), that is part of a message of type +messageType+
// via the registered message delegators
relaySDP = function(messageType, sdp, uri) {
delegateMessage(messageType, sdp, uri);
},
-
// Process an offer that
processOffer = function(message) {
var offer = new NativeRTCSessionDescription({type: 'offer', sdp: message.content.sdp}),
@@ -13480,9 +13906,9 @@ OT.PeerConnection = function(config) {
_answer = new NativeRTCSessionDescription({type: 'answer', sdp: message.content.sdp});
_peerConnection.setRemoteDescription(_answer,
- function () {
+ function() {
OT.debug('setRemoteDescription Success');
- }, function (errorReason) {
+ }, function(errorReason) {
triggerError('Error while setting RemoteDescription ' + errorReason,
'SetRemoteDescription');
});
@@ -13506,6 +13932,7 @@ OT.PeerConnection = function(config) {
createPeerConnection(function() {
subscribeProcessor(
_peerConnection,
+ config.numberOfSimulcastStreams,
// Success: Relay Offer
function(offer) {
@@ -13566,7 +13993,7 @@ OT.PeerConnection = function(config) {
OT.debug(message);
- switch(type) {
+ switch (type) {
case 'generateoffer':
processSubscribe(message);
break;
@@ -13592,7 +14019,7 @@ OT.PeerConnection = function(config) {
return api;
};
- api.setIceServers = function (iceServers) {
+ api.setIceServers = function(iceServers) {
if (iceServers) {
config.iceServers = iceServers;
}
@@ -13605,7 +14032,7 @@ OT.PeerConnection = function(config) {
api.unregisterMessageDelegate = function(delegateFn) {
var index = OT.$.arrayIndexOf(_messageDelegates, delegateFn);
- if ( index !== -1 ) {
+ if (index !== -1) {
_messageDelegates.splice(index, 1);
}
return _messageDelegates.length;
@@ -13616,18 +14043,21 @@ OT.PeerConnection = function(config) {
};
api.getStats = function(callback) {
- createPeerConnection(function() {
- _getStatsAdapter(_peerConnection, callback);
- });
+ if (!_peerConnection) {
+ callback(new OT.$.Error('Cannot call getStats before there is a connection.', 1015));
+ return;
+ }
+ _getStatsAdapter(_peerConnection, callback);
};
- var waitForChannel = function waitForChannel (timesToWait, label, options, completion) {
+ var waitForChannel = function waitForChannel(timesToWait, label, options, completion) {
var channel = _channels.get(label, options),
err;
if (!channel) {
if (timesToWait > 0) {
- setTimeout(OT.$.bind(waitForChannel, null, timesToWait-1, label, options, completion), 200);
+ setTimeout(OT.$.bind(waitForChannel, null, timesToWait - 1, label, options, completion),
+ 200);
return;
}
@@ -13638,11 +14068,13 @@ OT.PeerConnection = function(config) {
completion(err, channel);
};
- api.getDataChannel = function (label, options, completion) {
- createPeerConnection(function() {
- // Wait up to 20 sec for the channel to appear, then fail
- waitForChannel(100, label, options, completion);
- });
+ api.getDataChannel = function(label, options, completion) {
+ if (!_peerConnection) {
+ completion(new OT.$.Error('Cannot create a DataChannel before there is a connection.'));
+ return;
+ }
+ // Wait up to 20 sec for the channel to appear, then fail
+ waitForChannel(100, label, options, completion);
};
var qos = new OT.PeerConnection.QOS(qosCallback);
@@ -13676,13 +14108,13 @@ OT.PeerConnection = function(config) {
// Get Stats using the older API. Used by all current versions
// of Chrome.
//
- var parseStatsOldAPI = function parseStatsOldAPI (peerConnection,
- prevStats,
- currentStats,
- completion) {
+ var parseStatsOldAPI = function parseStatsOldAPI(peerConnection,
+ prevStats,
+ currentStats,
+ completion) {
/* this parses a result if there it contains the video bitrate */
- var parseVideoStats = function (result) {
+ var parseVideoStats = function(result) {
if (result.stat('googFrameRateSent')) {
currentStats.videoSentBytes = Number(result.stat('bytesSent'));
currentStats.videoSentPackets = Number(result.stat('packetsSent'));
@@ -13691,20 +14123,26 @@ OT.PeerConnection = function(config) {
currentStats.videoFrameRate = Number(result.stat('googFrameRateInput'));
currentStats.videoWidth = Number(result.stat('googFrameWidthSent'));
currentStats.videoHeight = Number(result.stat('googFrameHeightSent'));
+ currentStats.videoFrameRateSent = Number(result.stat('googFrameRateSent'));
+ currentStats.videoWidthInput = Number(result.stat('googFrameWidthInput'));
+ currentStats.videoHeightInput = Number(result.stat('googFrameHeightInput'));
currentStats.videoCodec = result.stat('googCodecName');
} else if (result.stat('googFrameRateReceived')) {
currentStats.videoRecvBytes = Number(result.stat('bytesReceived'));
currentStats.videoRecvPackets = Number(result.stat('packetsReceived'));
currentStats.videoRecvPacketsLost = Number(result.stat('packetsLost'));
currentStats.videoFrameRate = Number(result.stat('googFrameRateOutput'));
+ currentStats.videoFrameRateReceived = Number(result.stat('googFrameRateReceived'));
+ currentStats.videoFrameRateDecoded = Number(result.stat('googFrameRateDecoded'));
currentStats.videoWidth = Number(result.stat('googFrameWidthReceived'));
currentStats.videoHeight = Number(result.stat('googFrameHeightReceived'));
currentStats.videoCodec = result.stat('googCodecName');
}
+
return null;
},
- parseAudioStats = function (result) {
+ parseAudioStats = function(result) {
if (result.stat('audioInputLevel')) {
currentStats.audioSentPackets = Number(result.stat('packetsSent'));
currentStats.audioSentPacketsLost = Number(result.stat('packetsLost'));
@@ -13719,15 +14157,14 @@ OT.PeerConnection = function(config) {
}
},
- parseStatsReports = function (stats) {
+ parseStatsReports = function(stats) {
if (stats.result) {
var resultList = stats.result();
for (var resultIndex = 0; resultIndex < resultList.length; resultIndex++) {
var result = resultList[resultIndex];
if (result.stat) {
-
- if(result.stat('googActiveConnection') === 'true') {
+ if (result.stat('googActiveConnection') === 'true') {
currentStats.localCandidateType = result.stat('googLocalCandidateType');
currentStats.remoteCandidateType = result.stat('googRemoteCandidateType');
currentStats.transportType = result.stat('googTransportType');
@@ -13749,12 +14186,12 @@ OT.PeerConnection = function(config) {
// Get Stats for the OT Plugin, newer than Chromes version, but
// still not in sync with the spec.
//
- var parseStatsOTPlugin = function parseStatsOTPlugin (peerConnection,
+ var parseStatsOTPlugin = function parseStatsOTPlugin(peerConnection,
prevStats,
currentStats,
completion) {
- var onStatsError = function onStatsError (error) {
+ var onStatsError = function onStatsError(error) {
completion(error);
},
@@ -13763,7 +14200,7 @@ OT.PeerConnection = function(config) {
// * avgAudioBitrate
// * audioBytesTransferred
//
- parseAudioStats = function (statsReport) {
+ parseAudioStats = function(statsReport) {
var lastBytesSent = prevStats.audioBytesTransferred || 0,
transferDelta;
@@ -13773,8 +14210,7 @@ OT.PeerConnection = function(config) {
currentStats.audioSentPacketsLost = Number(statsReport.packetsLost);
currentStats.audioRtt = Number(statsReport.googRtt);
currentStats.audioCodec = statsReport.googCodecName;
- }
- else if (statsReport.audioOutputLevel) {
+ } else if (statsReport.audioOutputLevel) {
currentStats.audioBytesTransferred = Number(statsReport.bytesReceived);
currentStats.audioCodec = statsReport.googCodecName;
}
@@ -13782,7 +14218,7 @@ OT.PeerConnection = function(config) {
if (currentStats.audioBytesTransferred) {
transferDelta = currentStats.audioBytesTransferred - lastBytesSent;
currentStats.avgAudioBitrate = Math.round(transferDelta * 8 /
- (currentStats.period/1000));
+ (currentStats.period / 1000));
}
},
@@ -13792,7 +14228,7 @@ OT.PeerConnection = function(config) {
// * avgVideoBitrate
// * videoBytesTransferred
//
- parseVideoStats = function (statsReport) {
+ parseVideoStats = function(statsReport) {
var lastBytesSent = prevStats.videoBytesTransferred || 0,
transferDelta;
@@ -13805,13 +14241,17 @@ OT.PeerConnection = function(config) {
currentStats.videoCodec = statsReport.googCodecName;
currentStats.videoWidth = Number(statsReport.googFrameWidthSent);
currentStats.videoHeight = Number(statsReport.googFrameHeightSent);
- }
- else if (statsReport.googFrameHeightReceived) {
+ currentStats.videoFrameRateSent = Number(statsReport.googFrameRateSent);
+ currentStats.videoWidthInput = Number(statsReport.googFrameWidthInput);
+ currentStats.videoHeightInput = Number(statsReport.googFrameHeightInput);
+ } else if (statsReport.googFrameHeightReceived) {
currentStats.videoRecvBytes = Number(statsReport.bytesReceived);
currentStats.videoRecvPackets = Number(statsReport.packetsReceived);
currentStats.videoRecvPacketsLost = Number(statsReport.packetsLost);
currentStats.videoRtt = Number(statsReport.googRtt);
currentStats.videoCodec = statsReport.googCodecName;
+ currentStats.videoFrameRateReceived = Number(statsReport.googFrameRateReceived);
+ currentStats.videoFrameRateDecoded = Number(statsReport.googFrameRateDecoded);
currentStats.videoWidth = Number(statsReport.googFrameWidthReceived);
currentStats.videoHeight = Number(statsReport.googFrameHeightReceived);
}
@@ -13819,13 +14259,13 @@ OT.PeerConnection = function(config) {
if (currentStats.videoBytesTransferred) {
transferDelta = currentStats.videoBytesTransferred - lastBytesSent;
currentStats.avgVideoBitrate = Math.round(transferDelta * 8 /
- (currentStats.period/1000));
+ (currentStats.period / 1000));
}
- if (statsReport.googFrameRateSent) {
- currentStats.videoFrameRate = Number(statsReport.googFrameRateSent);
- } else if (statsReport.googFrameRateReceived) {
- currentStats.videoFrameRate = Number(statsReport.googFrameRateReceived);
+ if (statsReport.googFrameRateInput) {
+ currentStats.videoFrameRate = Number(statsReport.googFrameRateInput);
+ } else if (statsReport.googFrameRateOutput) {
+ currentStats.videoFrameRate = Number(statsReport.googFrameRateOutput);
}
},
@@ -13846,11 +14286,9 @@ OT.PeerConnection = function(config) {
currentStats.localCandidateType = statsReport.googLocalCandidateType;
currentStats.remoteCandidateType = statsReport.googRemoteCandidateType;
currentStats.transportType = statsReport.googTransportType;
- }
- else if (isStatsForVideoTrack(statsReport)) {
+ } else if (isStatsForVideoTrack(statsReport)) {
parseVideoStats(statsReport);
- }
- else {
+ } else {
parseAudioStats(statsReport);
}
});
@@ -13859,37 +14297,36 @@ OT.PeerConnection = function(config) {
}, onStatsError);
};
-
///
// Get Stats using the newer API.
//
- var parseStatsNewAPI = function parseStatsNewAPI (peerConnection,
- prevStats,
- currentStats,
- completion) {
+ var parseStatsNewAPI = function parseStatsNewAPI(peerConnection,
+ prevStats,
+ currentStats,
+ completion) {
- var onStatsError = function onStatsError (error) {
+ var onStatsError = function onStatsError(error) {
completion(error);
},
- parseAudioStats = function (result) {
- if (result.type==='outboundrtp') {
+ parseAudioStats = function(result) {
+ if (result.type === 'outboundrtp') {
currentStats.audioSentPackets = result.packetsSent;
currentStats.audioSentPacketsLost = result.packetsLost;
currentStats.audioSentBytes = result.bytesSent;
- } else if (result.type==='inboundrtp') {
+ } else if (result.type === 'inboundrtp') {
currentStats.audioRecvPackets = result.packetsReceived;
currentStats.audioRecvPacketsLost = result.packetsLost;
currentStats.audioRecvBytes = result.bytesReceived;
}
},
- parseVideoStats = function (result) {
- if (result.type==='outboundrtp') {
+ parseVideoStats = function(result) {
+ if (result.type === 'outboundrtp') {
currentStats.videoSentPackets = result.packetsSent;
currentStats.videoSentPacketsLost = result.packetsLost;
currentStats.videoSentBytes = result.bytesSent;
- } else if (result.type==='inboundrtp') {
+ } else if (result.type === 'inboundrtp') {
currentStats.videoRecvPackets = result.packetsReceived;
currentStats.videoRecvPacketsLost = result.packetsLost;
currentStats.videoRecvBytes = result.bytesReceived;
@@ -13917,27 +14354,24 @@ OT.PeerConnection = function(config) {
}, onStatsError);
};
-
- var parseQOS = function (peerConnection, prevStats, currentStats, completion) {
+ var parseQOS = function(peerConnection, prevStats, currentStats, completion) {
if (OTPlugin.isInstalled()) {
parseQOS = parseStatsOTPlugin;
return parseStatsOTPlugin(peerConnection, prevStats, currentStats, completion);
- }
- else if (OT.$.env.name === 'Firefox' && OT.$.env.version >= 27) {
+ } else if (OT.$.env.name === 'Firefox' && OT.$.env.version >= 27) {
parseQOS = parseStatsNewAPI;
return parseStatsNewAPI(peerConnection, prevStats, currentStats, completion);
- }
- else {
+ } else {
parseQOS = parseStatsOldAPI;
return parseStatsOldAPI(peerConnection, prevStats, currentStats, completion);
}
};
- OT.PeerConnection.QOS = function (qosCallback) {
+ OT.PeerConnection.QOS = function(qosCallback) {
var _creationTime = OT.$.now(),
_peerConnection;
- var calculateQOS = OT.$.bind(function calculateQOS (prevStats) {
+ var calculateQOS = OT.$.bind(function calculateQOS(prevStats, interval) {
if (!_peerConnection) {
// We don't have a PeerConnection yet, or we did and
// it's been closed. Either way we're done.
@@ -13952,7 +14386,7 @@ OT.PeerConnection = function(config) {
period: Math.round(now - prevStats.timeStamp)
};
- var onParsedStats = function (err, parsedStats) {
+ var onParsedStats = function(err, parsedStats) {
if (err) {
OT.error('Failed to Parse QOS Stats: ' + JSON.stringify(err));
return;
@@ -13960,15 +14394,19 @@ OT.PeerConnection = function(config) {
qosCallback(parsedStats, prevStats);
+ var nextInterval = interval === OT.PeerConnection.QOS.INITIAL_INTERVAL ?
+ OT.PeerConnection.QOS.INTERVAL - OT.PeerConnection.QOS.INITIAL_INTERVAL :
+ OT.PeerConnection.QOS.INTERVAL;
+
// Recalculate the stats
- setTimeout(OT.$.bind(calculateQOS, null, parsedStats), OT.PeerConnection.QOS.INTERVAL);
+ setTimeout(OT.$.bind(calculateQOS, null, parsedStats, nextInterval),
+ interval);
};
parseQOS(_peerConnection, prevStats, currentStats, onParsedStats);
}, this);
-
- this.startCollecting = function (peerConnection) {
+ this.startCollecting = function(peerConnection) {
if (!peerConnection || !peerConnection.getStats) {
// It looks like this browser doesn't support getStats
// Bail.
@@ -13977,17 +14415,17 @@ OT.PeerConnection = function(config) {
_peerConnection = peerConnection;
- calculateQOS({
- timeStamp: OT.$.now()
- });
+ calculateQOS({timeStamp: OT.$.now()}, OT.PeerConnection.QOS.INITIAL_INTERVAL);
};
- this.stopCollecting = function () {
+ this.stopCollecting = function() {
_peerConnection = null;
};
};
- // Recalculate the stats in 30 sec
+ // Send stats after 1 sec
+ OT.PeerConnection.QOS.INITIAL_INTERVAL = 1000;
+ // Recalculate the stats every 30 sec
OT.PeerConnection.QOS.INTERVAL = 30000;
})();
@@ -14030,9 +14468,13 @@ OT.PeerConnections = (function() {
}
};
})();
+
// tb_require('../../helpers/helpers.js')
// tb_require('../messaging/raptor/raptor.js')
// tb_require('./peer_connections.js')
+// tb_require('./set_certificates.js')
+
+/* global _setCertificates */
/*
* Abstracts PeerConnection related stuff away from OT.Subscriber.
@@ -14060,7 +14502,8 @@ OT.PeerConnections = (function() {
OT.SubscriberPeerConnection = function(remoteConnection, session, stream,
subscriber, properties) {
- var _peerConnection,
+ var _subscriberPeerConnection = this,
+ _peerConnection,
_destroyed = false,
_hasRelayCandidates = false,
_onPeerClosed,
@@ -14091,7 +14534,7 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream,
};
_relayMessageToPeer = OT.$.bind(function(type, payload) {
- if (!_hasRelayCandidates){
+ if (!_hasRelayCandidates) {
var extractCandidates = type === OT.Raptor.Actions.CANDIDATE ||
type === OT.Raptor.Actions.OFFER ||
type === OT.Raptor.Actions.ANSWER ||
@@ -14103,7 +14546,7 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream,
}
}
- switch(type) {
+ switch (type) {
case OT.Raptor.Actions.ANSWER:
case OT.Raptor.Actions.PRANSWER:
this.trigger('connected');
@@ -14136,11 +14579,11 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream,
return;
}
- for (var i=0, num=remoteStreams.length; i 0) {
- OT.$.forEach(_peerConnection.remoteStreams(), _onRemoteStreamAdded, this);
- } else if (numDelegates === 1) {
- // We only bother with the PeerConnection negotiation if we don't already
- // have a remote stream.
-
- var channelsToSubscribeTo;
-
- if (properties.subscribeToVideo || properties.subscribeToAudio) {
- var audio = stream.getChannelsOfType('audio'),
- video = stream.getChannelsOfType('video');
-
- channelsToSubscribeTo = OT.$.map(audio, function(channel) {
- return {
- id: channel.id,
- type: channel.type,
- active: properties.subscribeToAudio
- };
- }).concat(OT.$.map(video, function(channel) {
- return {
- id: channel.id,
- type: channel.type,
- active: properties.subscribeToVideo,
- restrictFrameRate: properties.restrictFrameRate !== void 0 ?
- properties.restrictFrameRate : false
- };
- }));
+ _setCertificates(pcConfig, function(err, pcConfigWithCerts) {
+ if (err) {
+ completion(err);
+ return;
}
- session._.subscriberCreate(stream, subscriber, channelsToSubscribeTo,
- OT.$.bind(function(err, message) {
- if (err) {
- this.trigger('error', err.message, this, 'Subscribe');
+ _peerConnection = OT.PeerConnections.add(
+ remoteConnection,
+ stream.streamId,
+ pcConfigWithCerts
+ );
+
+ _peerConnection.on({
+ close: _onPeerClosed,
+ streamAdded: _onRemoteStreamAdded,
+ streamRemoved: _onRemoteStreamRemoved,
+ error: _onPeerError,
+ qos: _onQOS
+ }, _subscriberPeerConnection);
+
+ var numDelegates = _peerConnection.registerMessageDelegate(_relayMessageToPeer);
+
+ // If there are already remoteStreams, add them immediately
+ if (_peerConnection.remoteStreams().length > 0) {
+ OT.$.forEach(
+ _peerConnection.remoteStreams(),
+ _onRemoteStreamAdded,
+ _subscriberPeerConnection
+ );
+ } else if (numDelegates === 1) {
+ // We only bother with the PeerConnection negotiation if we don't already
+ // have a remote stream.
+
+ var channelsToSubscribeTo;
+
+ if (properties.subscribeToVideo || properties.subscribeToAudio) {
+ var audio = stream.getChannelsOfType('audio'),
+ video = stream.getChannelsOfType('video');
+
+ channelsToSubscribeTo = OT.$.map(audio, function(channel) {
+ return {
+ id: channel.id,
+ type: channel.type,
+ active: properties.subscribeToAudio
+ };
+ }).concat(OT.$.map(video, function(channel) {
+ var props = {
+ id: channel.id,
+ type: channel.type,
+ active: properties.subscribeToVideo,
+ restrictFrameRate: properties.restrictFrameRate !== void 0 ?
+ properties.restrictFrameRate : false
+ };
+
+ if (properties.maxFrameRate !== void 0) {
+ props.maxFrameRate = parseFloat(properties.maxFrameRate);
+ }
+
+ if (properties.maxHeight !== void 0) {
+ props.maxHeight = parseInt(properties.maxHeight, 10);
+ }
+
+ if (properties.maxWidth !== void 0) {
+ props.maxWidth = parseInt(properties.maxWidth, 10);
+ }
+
+ return props;
+ }));
+ }
+
+ session._.subscriberCreate(
+ stream,
+ subscriber,
+ channelsToSubscribeTo,
+ function(err, message) {
+ if (err) {
+ _subscriberPeerConnection.trigger(
+ 'error',
+ err.message,
+ _subscriberPeerConnection,
+ 'Subscribe'
+ );
+ }
+ if (_peerConnection) {
+ _peerConnection.setIceServers(OT.Raptor.parseIceServers(message));
+ }
}
- if (_peerConnection) {
- _peerConnection.setIceServers(OT.Raptor.parseIceServers(message));
- }
- }, this));
- }
+ );
+
+ completion(undefined, _subscriberPeerConnection);
+ }
+ });
};
};
// tb_require('../../helpers/helpers.js')
// tb_require('../messaging/raptor/raptor.js')
// tb_require('./peer_connections.js')
+// tb_require('./set_certificates.js')
+/* global _setCertificates */
/*
* Abstracts PeerConnection related stuff away from OT.Publisher.
@@ -14282,8 +14769,10 @@ OT.SubscriberPeerConnection = function(remoteConnection, session, stream,
* * connected
* * disconnected
*/
-OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream, channels) {
- var _peerConnection,
+OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream, channels,
+ numberOfSimulcastStreams) {
+ var _publisherPeerConnection = this,
+ _peerConnection,
_hasRelayCandidates = false,
_subscriberId = session._.subscriberMap[remoteConnection.id + '_' + streamId],
_onPeerClosed,
@@ -14304,7 +14793,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
};
_relayMessageToPeer = OT.$.bind(function(type, payload, uri) {
- if (!_hasRelayCandidates){
+ if (!_hasRelayCandidates) {
var extractCandidates = type === OT.Raptor.Actions.CANDIDATE ||
type === OT.Raptor.Actions.OFFER ||
type === OT.Raptor.Actions.ANSWER ||
@@ -14316,7 +14805,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
}
}
- switch(type) {
+ switch (type) {
case OT.Raptor.Actions.ANSWER:
case OT.Raptor.Actions.PRANSWER:
if (session.sessionInfo.p2pEnabled) {
@@ -14343,7 +14832,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
}
}, this);
- _onQOS = OT.$.bind(function _onQOS (parsedStats, prevStats) {
+ _onQOS = OT.$.bind(function _onQOS(parsedStats, prevStats) {
this.trigger('qos', remoteConnection, parsedStats, prevStats);
}, this);
@@ -14351,7 +14840,7 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
/// Public API
- this.getDataChannel = function (label, options, completion) {
+ this.getDataChannel = function(label, options, completion) {
_peerConnection.getDataChannel(label, options, completion);
};
@@ -14370,21 +14859,33 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
};
// Init
- this.init = function(iceServers) {
- _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, {
+ this.init = function(iceServers, completion) {
+ var pcConfig = {
iceServers: iceServers,
- channels: channels
+ channels: channels,
+ numberOfSimulcastStreams: numberOfSimulcastStreams
+ };
+
+ _setCertificates(pcConfig, function(err, pcConfigWithCerts) {
+ if (err) {
+ completion(err);
+ return;
+ }
+
+ _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, pcConfigWithCerts);
+
+ _peerConnection.on({
+ close: _onPeerClosed,
+ error: _onPeerError,
+ qos: _onQOS
+ }, _publisherPeerConnection);
+
+ _peerConnection.registerMessageDelegate(_relayMessageToPeer);
+ _peerConnection.addLocalStream(webRTCStream);
+
+ completion(undefined, _publisherPeerConnection);
});
- _peerConnection.on({
- close: _onPeerClosed,
- error: _onPeerError,
- qos: _onQOS
- }, this);
-
- _peerConnection.registerMessageDelegate(_relayMessageToPeer);
- _peerConnection.addLocalStream(webRTCStream);
-
this.remoteConnection = function() {
return remoteConnection;
};
@@ -14392,7 +14893,6 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
this.hasRelayCandidates = function() {
return _hasRelayCandidates;
};
-
};
this.getSenders = function() {
@@ -14400,51 +14900,13 @@ OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRT
};
};
-
// tb_require('../helpers.js')
// tb_require('./web_rtc.js')
+// tb_require('./videoContentResizesMixin.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
-/* global OT, OTPlugin */
-
-var videoContentResizesMixin = function(self, domElement) {
-
- var width = domElement.videoWidth,
- height = domElement.videoHeight,
- stopped = true;
-
- function actor() {
- if (stopped) {
- return;
- }
- if (width !== domElement.videoWidth || height !== domElement.videoHeight) {
- self.trigger('videoDimensionsChanged',
- { width: width, height: height },
- { width: domElement.videoWidth, height: domElement.videoHeight }
- );
- width = domElement.videoWidth;
- height = domElement.videoHeight;
- }
- waiter();
- }
-
- function waiter() {
- self.whenTimeIncrements(function() {
- window.requestAnimationFrame(actor);
- });
- }
-
- self.startObservingSize = function() {
- stopped = false;
- waiter();
- };
-
- self.stopObservingSize = function() {
- stopped = true;
- };
-
-};
+/* global OT, OTPlugin, videoContentResizesMixin */
(function(window) {
@@ -14487,12 +14949,12 @@ var videoContentResizesMixin = function(self, domElement) {
//
//
OT.VideoElement = function(/* optional */ options/*, optional errorHandler*/) {
- var _options = OT.$.defaults( options && !OT.$.isFunction(options) ? options : {}, {
+ var _options = OT.$.defaults(options && !OT.$.isFunction(options) ? options : {}, {
fallbackText: 'Sorry, Web RTC is not available in your browser'
}),
- errorHandler = OT.$.isFunction(arguments[arguments.length-1]) ?
- arguments[arguments.length-1] : void 0,
+ errorHandler = OT.$.isFunction(arguments[arguments.length - 1]) ?
+ arguments[arguments.length - 1] : void 0,
orientationHandler = OT.$.bind(function(orientation) {
this.trigger('orientationChanged', orientation);
@@ -14503,7 +14965,7 @@ var videoContentResizesMixin = function(self, domElement) {
new NativeDOMVideoElement(_options, errorHandler, orientationHandler),
_streamBound = false,
_stream,
- _preInitialisedVolue;
+ _preInitialisedVolume;
OT.$.eventing(this);
@@ -14590,9 +15052,9 @@ var videoContentResizesMixin = function(self, domElement) {
_streamBound = true;
- if (_preInitialisedVolue) {
- this.setAudioVolume(_preInitialisedVolue);
- _preInitialisedVolue = null;
+ if (typeof _preInitialisedVolume !== 'undefined') {
+ this.setAudioVolume(_preInitialisedVolume);
+ _preInitialisedVolume = undefined;
}
_videoElement.on('aspectRatioAvailable',
@@ -14607,25 +15069,32 @@ var videoContentResizesMixin = function(self, domElement) {
this.unbindStream = function() {
if (!_stream) return this;
+ _stream.onended = null;
_stream = null;
_videoElement.unbindStream();
return this;
};
- this.setAudioVolume = function (value) {
- if (_streamBound) _videoElement.setAudioVolume( OT.$.roundFloat(value / 100, 2) );
- else _preInitialisedVolue = value;
+ this.setAudioVolume = function(value) {
+ if (_streamBound) _videoElement.setAudioVolume(OT.$.roundFloat(value / 100, 2));
+ else _preInitialisedVolume = parseFloat(value);
return this;
};
- this.getAudioVolume = function () {
- if (_streamBound) return parseInt(_videoElement.getAudioVolume() * 100, 10);
- else return _preInitialisedVolue || 50;
+ this.getAudioVolume = function() {
+ if (_streamBound) {
+ return parseInt(_videoElement.getAudioVolume() * 100, 10);
+ }
+
+ if (typeof _preInitialisedVolume !== 'undefined') {
+ return _preInitialisedVolume;
+ }
+
+ return 50;
};
-
- this.whenTimeIncrements = function (callback, context) {
+ this.whenTimeIncrements = function(callback, context) {
_videoElement.whenTimeIncrements(callback, context);
return this;
};
@@ -14635,7 +15104,7 @@ var videoContentResizesMixin = function(self, domElement) {
return this;
};
- this.destroy = function () {
+ this.destroy = function() {
// unbind all events so they don't fire after the object is dead
this.off();
@@ -14644,9 +15113,9 @@ var videoContentResizesMixin = function(self, domElement) {
};
};
- var PluginVideoElement = function PluginVideoElement (options,
- errorHandler,
- orientationChangedHandler) {
+ var PluginVideoElement = function PluginVideoElement(options,
+ errorHandler,
+ orientationChangedHandler) {
var _videoProxy,
_parentDomElement,
_ratioAvailable = false,
@@ -14742,7 +15211,7 @@ var videoContentResizesMixin = function(self, domElement) {
};
this.onRatioAvailable = function(callback) {
- if(_ratioAvailable) {
+ if (_ratioAvailable) {
callback();
} else {
_ratioAvailableListeners.push(callback);
@@ -14756,10 +15225,9 @@ var videoContentResizesMixin = function(self, domElement) {
};
};
-
- var NativeDOMVideoElement = function NativeDOMVideoElement (options,
- errorHandler,
- orientationChangedHandler) {
+ var NativeDOMVideoElement = function NativeDOMVideoElement(options,
+ errorHandler,
+ orientationChangedHandler) {
var _domElement,
_videoElementMovedWarning = false;
@@ -14776,7 +15244,7 @@ var videoContentResizesMixin = function(self, domElement) {
// unfortunate. This function plays the video again and is triggered
// on the pause event.
_playVideoOnPause = function() {
- if(!_videoElementMovedWarning) {
+ if (!_videoElementMovedWarning) {
OT.warn('Video element paused, auto-resuming. If you intended to do this, ' +
'use publishVideo(false) or subscribeToVideo(false) instead.');
@@ -14786,7 +15254,6 @@ var videoContentResizesMixin = function(self, domElement) {
_domElement.play();
};
-
_domElement = createNativeVideoElement(options.fallbackText, options.attributes);
// dirty but it is the only way right now to get the aspect ratio in FF
@@ -14839,7 +15306,7 @@ var videoContentResizesMixin = function(self, domElement) {
document.body.appendChild(canvas);
try {
canvas.getContext('2d').drawImage(_domElement, 0, 0, canvas.width, canvas.height);
- } catch(err) {
+ } catch (err) {
OT.warn('Cannot get image data yet');
return null;
}
@@ -14859,29 +15326,42 @@ var videoContentResizesMixin = function(self, domElement) {
// Bind a stream to the video element.
this.bindToStream = function(webRtcStream, completion) {
var _this = this;
+
bindStreamToNativeVideoElement(_domElement, webRtcStream, function(err) {
if (err) {
completion(err);
return;
}
+ if (!_domElement) {
+ completion('Can\'t bind because _domElement no longer exists');
+ return;
+ }
+
_this.startObservingSize();
- webRtcStream.onended = function() {
+ function handleEnded() {
+ webRtcStream.onended = null;
+
+ if (_domElement) {
+ _domElement.onended = null;
+ }
+
_this.trigger('mediaStopped', this);
- };
-
-
- if (_domElement) {
- _domElement.addEventListener('error', _onVideoError, false);
}
+
+ // OPENTOK-22428: Firefox doesn't emit the ended event on the webRtcStream when the user
+ // stops sharing their camera, but we do get the ended event on the video element.
+ webRtcStream.onended = handleEnded;
+ _domElement.onended = handleEnded;
+
+ _domElement.addEventListener('error', _onVideoError, false);
completion(null);
});
return this;
};
-
// Unbind the currently bound stream from the video element.
this.unbindStream = function() {
if (_domElement) {
@@ -14951,7 +15431,7 @@ var videoContentResizesMixin = function(self, domElement) {
};
this.onRatioAvailable = function(callback) {
- if(ratioAvailable) {
+ if (ratioAvailable) {
callback();
} else {
ratioAvailableListeners.push(callback);
@@ -14959,7 +15439,7 @@ var videoContentResizesMixin = function(self, domElement) {
};
};
-/// Private Helper functions
+ /// Private Helper functions
// A mixin to create the orientation API implementation on +self+
// +getDomElementCallback+ is a function that the mixin will call when it wants to
@@ -14968,10 +15448,10 @@ var videoContentResizesMixin = function(self, domElement) {
// +initialOrientation+ sets the initial orientation (shockingly), it's currently unused
// so the initial value is actually undefined.
//
- var canBeOrientatedMixin = function canBeOrientatedMixin (self,
- getDomElementCallback,
- orientationChangedHandler,
- initialOrientation) {
+ var canBeOrientatedMixin = function canBeOrientatedMixin(self,
+ getDomElementCallback,
+ orientationChangedHandler,
+ initialOrientation) {
var _orientation = initialOrientation;
OT.$.defineProperties(self, {
@@ -14991,7 +15471,7 @@ var videoContentResizesMixin = function(self, domElement) {
var transform = VideoOrientationTransforms[orientation.videoOrientation] ||
VideoOrientationTransforms.ROTATED_NORMAL;
- switch(OT.$.env.name) {
+ switch (OT.$.env.name) {
case 'Chrome':
case 'Safari':
getDomElementCallback().style.webkitTransform = transform;
@@ -15000,8 +15480,7 @@ var videoContentResizesMixin = function(self, domElement) {
case 'IE':
if (OT.$.env.version >= 9) {
getDomElementCallback().style.msTransform = transform;
- }
- else {
+ } else {
// So this basically defines matrix that represents a rotation
// of a single vector in a 2d basis.
//
@@ -15027,13 +15506,12 @@ var videoContentResizesMixin = function(self, domElement) {
// element.filters.item(0).M22 = costheta;
element.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(' +
- 'M11='+costheta+',' +
- 'M12='+(-sintheta)+',' +
- 'M21='+sintheta+',' +
- 'M22='+costheta+',SizingMethod=\'auto expand\')';
+ 'M11=' + costheta + ',' +
+ 'M12=' + (-sintheta) + ',' +
+ 'M21=' + sintheta + ',' +
+ 'M22=' + costheta + ',SizingMethod=\'auto expand\')';
}
-
break;
default:
@@ -15078,7 +15556,7 @@ var videoContentResizesMixin = function(self, domElement) {
}
for (var key in attributes) {
- if(!attributes.hasOwnProperty(key)) {
+ if (!attributes.hasOwnProperty(key)) {
continue;
}
videoElement.setAttribute(key, attributes[key]);
@@ -15088,7 +15566,6 @@ var videoContentResizesMixin = function(self, domElement) {
return videoElement;
}
-
// See http://www.w3.org/TR/2010/WD-html5-20101019/video.html#error-codes
var _videoErrorCodes = {};
@@ -15117,26 +15594,27 @@ var videoContentResizesMixin = function(self, domElement) {
loadedEvent = webRtcStream.getVideoTracks().length > minVideoTracksForTimeUpdate ?
'timeupdate' : 'loadedmetadata';
- var cleanup = function cleanup () {
+ var cleanup = function cleanup() {
clearTimeout(timeout);
videoElement.removeEventListener(loadedEvent, onLoad, false);
videoElement.removeEventListener('error', onError, false);
webRtcStream.onended = null;
+ videoElement.onended = null;
},
- onLoad = function onLoad () {
+ onLoad = function onLoad() {
cleanup();
completion(null);
},
- onError = function onError (event) {
+ onError = function onError(event) {
cleanup();
unbindNativeStream(videoElement);
completion('There was an unexpected problem with the Video Stream: ' +
videoElementErrorCodeToStr(event.target.error.code));
},
- onStoppedLoading = function onStoppedLoading () {
+ onStoppedLoading = function onStoppedLoading() {
// The stream ended before we fully bound it. Maybe the other end called
// stop on it or something else went wrong.
cleanup();
@@ -15144,10 +15622,6 @@ var videoContentResizesMixin = function(self, domElement) {
completion('Stream ended while trying to bind it to a video element.');
};
- videoElement.addEventListener(loadedEvent, onLoad, false);
- videoElement.addEventListener('error', onError, false);
- webRtcStream.onended = onStoppedLoading;
-
// The official spec way is 'srcObject', we are slowly converging there.
if (videoElement.srcObject !== void 0) {
videoElement.srcObject = webRtcStream;
@@ -15156,10 +15630,16 @@ var videoContentResizesMixin = function(self, domElement) {
} else {
videoElement.src = window.URL.createObjectURL(webRtcStream);
}
+
+ videoElement.addEventListener(loadedEvent, onLoad, false);
+ videoElement.addEventListener('error', onError, false);
+ webRtcStream.onended = onStoppedLoading;
+ videoElement.onended = onStoppedLoading;
}
-
function unbindNativeStream(videoElement) {
+ videoElement.onended = null;
+
if (videoElement.srcObject !== void 0) {
videoElement.srcObject = null;
} else if (videoElement.mozSrcObject !== void 0) {
@@ -15169,7 +15649,6 @@ var videoContentResizesMixin = function(self, domElement) {
}
}
-
})(window);
// tb_require('../helpers.js')
@@ -15180,9 +15659,9 @@ var videoContentResizesMixin = function(self, domElement) {
/* global OT */
!(function() {
-/*global OT:true */
+ /*global OT:true */
- var defaultAspectRatio = 4.0/3.0,
+ var defaultAspectRatio = 4.0 / 3.0,
miniWidth = 128,
miniHeight = 128,
microWidth = 64,
@@ -15267,7 +15746,6 @@ var videoContentResizesMixin = function(self, domElement) {
var cssProps = {left: '', top: ''};
-
if (OTPlugin.isInstalled()) {
intrinsicRatio = intrinsicRatio || defaultAspectRatio;
@@ -15310,12 +15788,12 @@ var videoContentResizesMixin = function(self, domElement) {
var w = parseInt(width, 10),
h = parseInt(height, 10);
- if(w < microWidth || h < microHeight) {
+ if (w < microWidth || h < microHeight) {
OT.$.addClass(container, 'OT_micro');
} else {
OT.$.removeClass(container, 'OT_micro');
}
- if(w < miniWidth || h < miniHeight) {
+ if (w < miniWidth || h < miniHeight) {
OT.$.addClass(container, 'OT_mini');
} else {
OT.$.removeClass(container, 'OT_mini');
@@ -15350,16 +15828,16 @@ var videoContentResizesMixin = function(self, domElement) {
container.style.backgroundColor = '#000000';
document.body.appendChild(container);
} else {
- if(!(insertMode == null || insertMode === 'replace')) {
+ if (!(insertMode == null || insertMode === 'replace')) {
var placeholder = document.createElement('div');
placeholder.id = ('OT_' + OT.$.uuid());
- if(insertMode === 'append') {
+ if (insertMode === 'append') {
container.appendChild(placeholder);
container = placeholder;
- } else if(insertMode === 'before') {
+ } else if (insertMode === 'before') {
container.parentNode.insertBefore(placeholder, container);
container = placeholder;
- } else if(insertMode === 'after') {
+ } else if (insertMode === 'after') {
container.parentNode.insertBefore(placeholder, container.nextSibling);
container = placeholder;
}
@@ -15399,13 +15877,13 @@ var videoContentResizesMixin = function(self, domElement) {
height = properties.height;
if (width) {
- if (typeof(width) === 'number') {
+ if (typeof width === 'number') {
width = width + 'px';
}
}
if (height) {
- if (typeof(height) === 'number') {
+ if (typeof height === 'number') {
height = height + 'px';
}
}
@@ -15463,7 +15941,6 @@ var videoContentResizesMixin = function(self, domElement) {
}
})[0];
-
// @todo observe if the video container or the video element get removed
// if they do we should do some cleanup
videoObserver = OT.$.observeNodeOrChildNodeRemoval(container, function(removedNodes) {
@@ -15544,6 +16021,11 @@ var videoContentResizesMixin = function(self, domElement) {
widgetView.bindVideo = function(webRTCStream, options, completion) {
// remove the old video element if it exists
// @todo this might not be safe, publishers/subscribers use this as well...
+
+ if (typeof options.audioVolume !== 'undefined') {
+ options.audioVolume = parseFloat(options.audioVolume);
+ }
+
if (videoElement) {
videoElement.destroy();
videoElement = null;
@@ -15555,7 +16037,9 @@ var videoContentResizesMixin = function(self, domElement) {
var video = new OT.VideoElement({attributes: options}, onError);
// Initialize the audio volume
- if (options.audioVolume) video.setAudioVolume(options.audioVolume);
+ if (typeof options.audioVolume !== 'undefined') {
+ video.setAudioVolume(options.audioVolume);
+ }
// makes the incoming audio streams take priority (will impact only FF OS for now)
video.audioChannelType('telephony');
@@ -15612,7 +16096,7 @@ var videoContentResizesMixin = function(self, domElement) {
return !OT.$.isDisplayNone(posterContainer);
},
set: function(newValue) {
- if(newValue) {
+ if (newValue) {
OT.$.show(posterContainer);
} else {
OT.$.hide(posterContainer);
@@ -15673,7 +16157,7 @@ var videoContentResizesMixin = function(self, domElement) {
(helpMsg ? ' ' + helpMsg + '' : '') +
'
';
OT.$.addClass(container, classNames || 'OT_subscriber_error');
- if(container.querySelector('p').offsetHeight > container.offsetHeight) {
+ if (container.querySelector('p').offsetHeight > container.offsetHeight) {
container.querySelector('span').style.display = 'none';
}
};
@@ -15694,13 +16178,8 @@ if (!OT.properties) {
'properties file.');
}
-OT.useSSL = function () {
- return OT.properties.supportSSL && (window.location.protocol.indexOf('https') >= 0 ||
- window.location.protocol.indexOf('chrome-extension') >= 0);
-};
-
// Consumes and overwrites OT.properties. Makes it better and stronger!
-OT.properties = function(properties) {
+OT.properties = (function(properties) {
var props = OT.$.clone(properties);
props.debug = properties.debug === 'true' || properties.debug === true;
@@ -15716,7 +16195,7 @@ OT.properties = function(properties) {
}
if (!props.assetURL) {
- if (OT.useSSL()) {
+ if (OT.properties.supportSSL) {
props.assetURL = props.cdnURLSSL + '/webrtc/' + props.version;
} else {
props.assetURL = props.cdnURL + '/webrtc/' + props.version;
@@ -15733,7 +16212,7 @@ OT.properties = function(properties) {
if (!props.cssURL) props.cssURL = props.assetURL + '/css/TB.min.css';
return props;
-}(OT.properties);
+})(OT.properties);
// tb_require('../helpers.js')
@@ -15745,11 +16224,11 @@ OT.properties = function(properties) {
var currentGuidStorage,
currentGuid;
- var isInvalidStorage = function isInvalidStorage (storageInterface) {
+ var isInvalidStorage = function isInvalidStorage(storageInterface) {
return !(OT.$.isFunction(storageInterface.get) && OT.$.isFunction(storageInterface.set));
};
- var getClientGuid = function getClientGuid (completion) {
+ var getClientGuid = function getClientGuid(completion) {
if (currentGuid) {
completion(null, currentGuid);
return;
@@ -15798,7 +16277,7 @@ OT.properties = function(properties) {
* OT.overrideGuidStorage(new ComplexStorage());
*
*/
- OT.overrideGuidStorage = function (storageInterface) {
+ OT.overrideGuidStorage = function(storageInterface) {
if (isInvalidStorage(storageInterface)) {
throw new Error('The storageInterface argument does not seem to be valid, ' +
'it must implement get and set methods');
@@ -15824,7 +16303,7 @@ OT.properties = function(properties) {
};
if (!OT._) OT._ = {};
- OT._.getClientGuid = function (completion) {
+ OT._.getClientGuid = function(completion) {
getClientGuid(function(error, guid) {
if (error) {
completion(error);
@@ -15843,8 +16322,7 @@ OT.properties = function(properties) {
currentGuid = guid;
});
- }
- else if (!currentGuid) {
+ } else if (!currentGuid) {
currentGuid = guid;
}
@@ -15852,7 +16330,6 @@ OT.properties = function(properties) {
});
};
-
// Implement our default storage mechanism, which sets/gets a cookie
// called 'opentok_client_id'
OT.overrideGuidStorage({
@@ -15926,7 +16403,7 @@ OT.properties = function(properties) {
mapVendorErrorName = function mapVendorErrorName(vendorErrorName, vendorErrors) {
var errorName, errorMessage;
- if(vendorErrors.hasOwnProperty(vendorErrorName)) {
+ if (vendorErrors.hasOwnProperty(vendorErrorName)) {
errorName = vendorErrors[vendorErrorName];
} else {
// This doesn't map to a known error from the Media Capture spec, it's
@@ -15934,7 +16411,7 @@ OT.properties = function(properties) {
errorName = vendorErrorName;
}
- if(gumNamesToMessages.hasOwnProperty(errorName)) {
+ if (gumNamesToMessages.hasOwnProperty(errorName)) {
errorMessage = gumNamesToMessages[errorName];
} else {
errorMessage = 'Unknown Error while getting user media';
@@ -15971,7 +16448,7 @@ OT.properties = function(properties) {
if (!constraints || !OT.$.isObject(constraints)) return true;
for (var key in constraints) {
- if(!constraints.hasOwnProperty(key)) {
+ if (!constraints.hasOwnProperty(key)) {
continue;
}
if (constraints[key]) return false;
@@ -15980,7 +16457,6 @@ OT.properties = function(properties) {
return true;
};
-
// A wrapper for the builtin navigator.getUserMedia. In addition to the usual
// getUserMedia behaviour, this helper method also accepts a accessDialogOpened
// and accessDialogClosed callback.
@@ -16016,7 +16492,7 @@ OT.properties = function(properties) {
var getUserMedia = nativeGetUserMedia;
- if(OT.$.isFunction(customGetUserMedia)) {
+ if (OT.$.isFunction(customGetUserMedia)) {
getUserMedia = customGetUserMedia;
}
@@ -16100,6 +16576,138 @@ OT.properties = function(properties) {
})();
+// tb_require('../helpers.js')
+// tb_require('./web_rtc.js')
+
+// Web OT Helpers
+!(function(window) {
+
+ /* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+ /* global OT */
+
+ ///
+ // Device Helpers
+ //
+ // Support functions to enumerating and guerying device info
+ //
+
+ // Map all the various formats for device kinds
+ // to the ones that our API exposes. Also includes
+ // an identity map.
+ var deviceKindsMap = {
+ audio: 'audioInput',
+ video: 'videoInput',
+ audioinput: 'audioInput',
+ videoinput: 'videoInput',
+ audioInput: 'audioInput',
+ videoInput: 'videoInput'
+ };
+
+ var getNativeEnumerateDevices = function() {
+ if (navigator.mediaDevices) {
+ return navigator.mediaDevices.enumerateDevices;
+ } else if (OT.$.hasCapabilities('otplugin')) {
+ return OTPlugin.mediaDevices.enumerateDevices;
+ } else if (window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
+ return window.MediaStreamTrack.getSources;
+ }
+ };
+
+ var enumerateDevices = function(completion) {
+ var fn = getNativeEnumerateDevices();
+
+ if (navigator.mediaDevices && OT.$.isFunction(fn)) {
+ // De-promisify the newer style APIs. We aren't ready for Promises yet...
+ fn = function(completion) {
+ navigator.mediaDevices.enumerateDevices().then(function(devices) {
+ completion(void 0, devices);
+ })['catch'](function(err) {
+ completion(err);
+ });
+ };
+ } else if (window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
+ fn = function(completion) {
+ window.MediaStreamTrack.getSources(function(devices) {
+ completion(void 0, devices.map(function(device) {
+ return OT.$.clone(device);
+ }));
+ });
+ };
+ }
+
+ return fn(completion);
+ };
+
+ var fakeShouldAskForDevices = function fakeShouldAskForDevices(callback) {
+ setTimeout(OT.$.bind(callback, null, { video: true, audio: true }));
+ };
+
+ // Indicates whether this browser supports the enumerateDevices (getSources) API.
+ //
+ OT.$.registerCapability('enumerateDevices', function() {
+ return OT.$.isFunction(getNativeEnumerateDevices());
+ });
+
+ OT.$.getMediaDevices = function(completion, customGetDevices) {
+ if (!OT.$.hasCapabilities('enumerateDevices')) {
+ completion(new Error('This browser does not support enumerateDevices APIs'));
+ return;
+ }
+
+ var getDevices = OT.$.isFunction(customGetDevices) ? customGetDevices
+ : enumerateDevices;
+
+ getDevices(function(err, devices) {
+ if (err) {
+ completion(err);
+ return;
+ }
+
+ // Normalise the device kinds
+ var filteredDevices = OT.$(devices).map(function(device) {
+ return {
+ deviceId: device.deviceId || device.id,
+ label: device.label,
+ kind: deviceKindsMap[device.kind]
+ };
+ }).filter(function(device) {
+ return device.kind === 'audioInput' || device.kind === 'videoInput';
+ });
+
+ completion(void 0, filteredDevices.get());
+ });
+ };
+
+ OT.$.shouldAskForDevices = function(callback) {
+ if (OT.$.hasCapabilities('enumerateDevices')) {
+ OT.$.getMediaDevices(function(err, devices) {
+ if (err) {
+ // There was an error. It may be temporally. Just assume
+ // all devices exist for now.
+ fakeShouldAskForDevices(callback);
+ return;
+ }
+
+ var hasAudio = OT.$.some(devices, function(device) {
+ return device.kind === 'audioInput';
+ });
+
+ var hasVideo = OT.$.some(devices, function(device) {
+ return device.kind === 'videoInput';
+ });
+
+ callback.call(null, { video: hasVideo, audio: hasAudio });
+ });
+
+ } else {
+ // This environment can't enumerate devices anyway, so we'll memorise this result.
+ OT.$.shouldAskForDevices = fakeShouldAskForDevices;
+ OT.$.shouldAskForDevices(callback);
+ }
+ };
+})(window);
+
// tb_require('../helpers.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
@@ -16139,7 +16747,7 @@ OT.properties = function(properties) {
var remainingStylesheets = allURLs.length;
OT.$.forEach(allURLs, function(stylesheetUrl) {
addCss(document, stylesheetUrl, function() {
- if(--remainingStylesheets <= 0) {
+ if (--remainingStylesheets <= 0) {
callback();
}
});
@@ -16154,7 +16762,7 @@ OT.properties = function(properties) {
return el;
};
- var checkBoxElement = function (classes, nameAndId, onChange) {
+ var checkBoxElement = function(classes, nameAndId, onChange) {
var checkbox = templateElement.call(this, '', null, 'input');
checkbox = OT.$(checkbox).on('change', onChange);
@@ -16235,8 +16843,7 @@ OT.properties = function(properties) {
function onToggleEULA() {
if (checkbox.checked) {
enableButtons();
- }
- else {
+ } else {
disableButtons();
}
}
@@ -16399,18 +17006,18 @@ OT.properties = function(properties) {
addDialogCSS(document, [], function() {
document.body.appendChild(root);
- if(progressValue != null) {
+ if (progressValue != null) {
modal.setUpdateProgress(progressValue);
}
});
}));
modal.setUpdateProgress = function(newProgress) {
- if(progressBar && progressText) {
- if(newProgress > 99) {
+ if (progressBar && progressText) {
+ if (newProgress > 99) {
OT.$.css(progressBar, 'width', '');
progressText.innerHTML = '100%';
- } else if(newProgress < 1) {
+ } else if (newProgress < 1) {
OT.$.css(progressBar, 'width', '0%');
progressText.innerHTML = '0%';
} else {
@@ -16439,7 +17046,7 @@ OT.properties = function(properties) {
var msgs;
- if(error) {
+ if (error) {
msgs = ['Update Failed.', error + '' || 'NO ERROR'];
} else {
msgs = ['Update Complete.',
@@ -16469,78 +17076,8 @@ OT.properties = function(properties) {
};
-
})();
-// tb_require('../helpers.js')
-// tb_require('./web_rtc.js')
-
-// Web OT Helpers
-!(function(window) {
-
- /* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
- /* global OT */
-
- ///
- // Device Helpers
- //
- // Support functions to enumerating and guerying device info
- //
-
- var chromeToW3CDeviceKinds = {
- audio: 'audioInput',
- video: 'videoInput'
- };
-
-
- OT.$.shouldAskForDevices = function(callback) {
- var MST = window.MediaStreamTrack;
-
- if(MST != null && OT.$.isFunction(MST.getSources)) {
- window.MediaStreamTrack.getSources(function(sources) {
- var hasAudio = sources.some(function(src) {
- return src.kind === 'audio';
- });
-
- var hasVideo = sources.some(function(src) {
- return src.kind === 'video';
- });
-
- callback.call(null, { video: hasVideo, audio: hasAudio });
- });
-
- } else {
- // This environment can't enumerate devices anyway, so we'll memorise this result.
- OT.$.shouldAskForDevices = function(callback) {
- setTimeout(OT.$.bind(callback, null, { video: true, audio: true }));
- };
-
- OT.$.shouldAskForDevices(callback);
- }
- };
-
-
- OT.$.getMediaDevices = function(callback) {
- if(OT.$.hasCapabilities('getMediaDevices')) {
- window.MediaStreamTrack.getSources(function(sources) {
- var filteredSources = OT.$.filter(sources, function(source) {
- return chromeToW3CDeviceKinds[source.kind] != null;
- });
- callback(void 0, OT.$.map(filteredSources, function(source) {
- return {
- deviceId: source.id,
- label: source.label,
- kind: chromeToW3CDeviceKinds[source.kind]
- };
- }));
- });
- } else {
- callback(new Error('This browser does not support getMediaDevices APIs'));
- }
- };
-
-})(window);
// tb_require('../helpers.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
@@ -16557,140 +17094,6 @@ var loadCSS = function loadCSS(cssURL) {
head.appendChild(style);
};
-// tb_require('../helpers.js')
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OTPlugin, OT */
-
-///
-// Capabilities
-//
-// Support functions to query browser/client Media capabilities.
-//
-
-
-// Indicates whether this client supports the getUserMedia
-// API.
-//
-OT.$.registerCapability('getUserMedia', function() {
- if (OT.$.env === 'Node') return false;
- return !!(navigator.webkitGetUserMedia ||
- navigator.mozGetUserMedia ||
- OTPlugin.isInstalled());
-});
-
-
-
-// TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer.
-// Indicates whether this client supports the PeerConnection
-// API.
-//
-// Chrome Issues:
-// * The explicit prototype.addStream check is because webkitRTCPeerConnection was
-// partially implemented, but not functional, in Chrome 22.
-//
-// Firefox Issues:
-// * No real support before Firefox 19
-// * Firefox 19 has issues with generating Offers.
-// * Firefox 20 doesn't interoperate with Chrome.
-//
-OT.$.registerCapability('PeerConnection', function() {
- if (OT.$.env === 'Node') {
- return false;
- }
- else if (typeof(window.webkitRTCPeerConnection) === 'function' &&
- !!window.webkitRTCPeerConnection.prototype.addStream) {
- return true;
- } else if (typeof(window.mozRTCPeerConnection) === 'function' && OT.$.env.version > 20.0) {
- return true;
- } else {
- return OTPlugin.isInstalled();
- }
-});
-
-
-
-// Indicates whether this client supports WebRTC
-//
-// This is defined as: getUserMedia + PeerConnection + exceeds min browser version
-//
-OT.$.registerCapability('webrtc', function() {
- if (OT.properties) {
- var minimumVersions = OT.properties.minimumVersion || {},
- minimumVersion = minimumVersions[OT.$.env.name.toLowerCase()];
-
- if(minimumVersion && OT.$.env.versionGreaterThan(minimumVersion)) {
- OT.debug('Support for', OT.$.env.name, 'is disabled because we require',
- minimumVersion, 'but this is', OT.$.env.version);
- return false;
- }
- }
-
- if (OT.$.env === 'Node') {
- // Node works, even though it doesn't have getUserMedia
- return true;
- }
-
- return OT.$.hasCapabilities('getUserMedia', 'PeerConnection');
-});
-
-
-// TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer.
-// Indicates if the browser supports bundle
-//
-// Broadly:
-// * Firefox doesn't support bundle
-// * Chrome support bundle
-// * OT Plugin supports bundle
-// * We assume NodeJs supports bundle (e.g. 'you're on your own' mode)
-//
-OT.$.registerCapability('bundle', function() {
- return OT.$.hasCapabilities('webrtc') &&
- (OT.$.env.name === 'Chrome' ||
- OT.$.env.name === 'Node' ||
- OTPlugin.isInstalled());
-});
-
-// Indicates if the browser supports RTCP Mux
-//
-// Broadly:
-// * Older versions of Firefox (<= 25) don't support RTCP Mux
-// * Older versions of Firefox (>= 26) support RTCP Mux (not tested yet)
-// * Chrome support RTCP Mux
-// * OT Plugin supports RTCP Mux
-// * We assume NodeJs supports RTCP Mux (e.g. 'you're on your own' mode)
-//
-OT.$.registerCapability('RTCPMux', function() {
- return OT.$.hasCapabilities('webrtc') &&
- (OT.$.env.name === 'Chrome' ||
- OT.$.env.name === 'Node' ||
- OTPlugin.isInstalled());
-});
-
-
-
-// Indicates whether this browser supports the getMediaDevices (getSources) API.
-//
-OT.$.registerCapability('getMediaDevices', function() {
- return OT.$.isFunction(window.MediaStreamTrack) &&
- OT.$.isFunction(window.MediaStreamTrack.getSources);
-});
-
-
-OT.$.registerCapability('audioOutputLevelStat', function() {
- return OT.$.env.name === 'Chrome' || OT.$.env.name === 'IE';
-});
-
-OT.$.registerCapability('webAudioCapableRemoteStream', function() {
- return OT.$.env.name === 'Firefox';
-});
-
-OT.$.registerCapability('webAudio', function() {
- return 'AudioContext' in window;
-});
-
-
// tb_require('../helpers.js')
// tb_require('./properties.js')
@@ -16723,8 +17126,8 @@ OT.Config = (function() {
if (_script) {
_script.onload = _script.onreadystatechange = null;
- if ( _head && _script.parentNode ) {
- _head.removeChild( _script );
+ if (_head && _script.parentNode) {
+ _head.removeChild(_script);
}
_script = undefined;
@@ -16733,8 +17136,8 @@ OT.Config = (function() {
_onLoad = function() {
// Only IE and Opera actually support readyState on Script elements.
- if (_script.readyState && !/loaded|complete/.test( _script.readyState )) {
- // Yeah, we're not ready yet...
+ if (_script.readyState && !/loaded|complete/.test(_script.readyState)) {
+ // Yeah, we're not ready yet...
return;
}
@@ -16782,20 +17185,19 @@ OT.Config = (function() {
_loaded = false;
setTimeout(function() {
- _script = document.createElement( 'script' );
+ _script = document.createElement('script');
_script.async = 'async';
_script.src = configUrl;
_script.onload = _script.onreadystatechange = OT.$.bind(_onLoad, _this);
_script.onerror = OT.$.bind(_onLoadError, _this);
_head.appendChild(_script);
- },1);
+ }, 1);
_loadTimer = setTimeout(function() {
_this._onLoadTimeout();
}, this.loadTimeout);
},
-
isLoaded: function() {
return _loaded;
},
@@ -16840,6 +17242,122 @@ OT.Config = (function() {
return _this;
})();
+// tb_require('../helpers.js')
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global OTPlugin, OT */
+
+///
+// Capabilities
+//
+// Support functions to query browser/client Media capabilities.
+//
+
+// Indicates whether this client supports the getUserMedia
+// API.
+//
+OT.$.registerCapability('getUserMedia', function() {
+ if (OT.$.env === 'Node') return false;
+ return !!(navigator.webkitGetUserMedia ||
+ navigator.mozGetUserMedia ||
+ OTPlugin.isInstalled());
+});
+
+// TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer.
+// Indicates whether this client supports the PeerConnection
+// API.
+//
+// Chrome Issues:
+// * The explicit prototype.addStream check is because webkitRTCPeerConnection was
+// partially implemented, but not functional, in Chrome 22.
+//
+// Firefox Issues:
+// * No real support before Firefox 19
+// * Firefox 19 has issues with generating Offers.
+// * Firefox 20 doesn't interoperate with Chrome.
+//
+OT.$.registerCapability('PeerConnection', function() {
+ if (OT.$.env === 'Node') {
+ return false;
+ } else if (typeof window.webkitRTCPeerConnection === 'function' &&
+ !!window.webkitRTCPeerConnection.prototype.addStream) {
+ return true;
+ } else if (typeof window.mozRTCPeerConnection === 'function' && OT.$.env.version > 20.0) {
+ return true;
+ } else {
+ return OTPlugin.isInstalled();
+ }
+});
+
+// Indicates whether this client supports WebRTC
+//
+// This is defined as: getUserMedia + PeerConnection + exceeds min browser version
+//
+OT.$.registerCapability('webrtc', function() {
+ if (OT.properties) {
+ var minimumVersions = OT.properties.minimumVersion || {},
+ minimumVersion = minimumVersions[OT.$.env.name.toLowerCase()];
+
+ if (minimumVersion && OT.$.env.versionGreaterThan(minimumVersion)) {
+ OT.debug('Support for', OT.$.env.name, 'is disabled because we require',
+ minimumVersion, 'but this is', OT.$.env.version);
+ return false;
+ }
+ }
+
+ if (OT.$.env === 'Node') {
+ // Node works, even though it doesn't have getUserMedia
+ return true;
+ }
+
+ return OT.$.hasCapabilities('getUserMedia', 'PeerConnection');
+});
+
+// TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer.
+// Indicates if the browser supports bundle
+//
+// Broadly:
+// * Firefox doesn't support bundle
+// * Chrome support bundle
+// * OT Plugin supports bundle
+// * We assume NodeJs supports bundle (e.g. 'you're on your own' mode)
+//
+OT.$.registerCapability('bundle', function() {
+ return OT.$.hasCapabilities('webrtc') &&
+ (OT.$.env.name === 'Chrome' ||
+ OT.$.env.name === 'Node' ||
+ OTPlugin.isInstalled());
+});
+
+// Indicates if the browser supports RTCP Mux
+//
+// Broadly:
+// * Older versions of Firefox (<= 25) don't support RTCP Mux
+// * Older versions of Firefox (>= 26) support RTCP Mux (not tested yet)
+// * Chrome support RTCP Mux
+// * OT Plugin supports RTCP Mux
+// * We assume NodeJs supports RTCP Mux (e.g. 'you're on your own' mode)
+//
+OT.$.registerCapability('RTCPMux', function() {
+ return OT.$.hasCapabilities('webrtc') &&
+ (OT.$.env.name === 'Chrome' ||
+ OT.$.env.name === 'Node' ||
+ OTPlugin.isInstalled());
+});
+
+OT.$.registerCapability('audioOutputLevelStat', function() {
+ return OT.$.env.name === 'Chrome' || OT.$.env.name === 'IE' || OT.$.env.name === 'Opera';
+});
+
+OT.$.registerCapability('webAudioCapableRemoteStream', function() {
+ return OT.$.env.name === 'Firefox';
+});
+
+OT.$.registerCapability('webAudio', function() {
+ return 'AudioContext' in window;
+});
+
// tb_require('../helpers.js')
// tb_require('./config.js')
@@ -16847,7 +17365,6 @@ OT.Config = (function() {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
OT.Analytics = function(loggingUrl) {
var LOG_VERSION = '1';
@@ -16868,19 +17385,19 @@ OT.Analytics = function(loggingUrl) {
}
var data = OT.$.extend({
// TODO: OT.properties.version only gives '2.2', not '2.2.9.3'.
- 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''),
- 'guid' : guid,
- 'partnerId' : partnerId,
- 'source' : window.location.href,
- 'logVersion' : LOG_VERSION,
- 'clientSystemTime' : new Date().getTime()
+ clientVersion: 'js-' + OT.properties.version.replace('v', ''),
+ guid: guid,
+ partnerId: partnerId,
+ source: window.location.href,
+ logVersion: LOG_VERSION,
+ clientSystemTime: new Date().getTime()
}, options);
_analytics.logError(code, type, message, details, data);
});
};
- this.logEvent = function(options, throttle) {
+ this.logEvent = function(options, throttle, completionHandler) {
var partnerId = options.partnerId;
if (!options) options = {};
@@ -16894,14 +17411,14 @@ OT.Analytics = function(loggingUrl) {
// Set a bunch of defaults
var data = OT.$.extend({
// TODO: OT.properties.version only gives '2.2', not '2.2.9.3'.
- 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''),
- 'guid' : guid,
- 'partnerId' : partnerId,
- 'source' : window.location.href,
- 'logVersion' : LOG_VERSION,
- 'clientSystemTime' : new Date().getTime()
+ clientVersion: 'js-' + OT.properties.version.replace('v', ''),
+ guid: guid,
+ partnerId: partnerId,
+ source: window.location.href,
+ logVersion: LOG_VERSION,
+ clientSystemTime: new Date().getTime()
}, options);
- _analytics.logEvent(data, false, throttle);
+ _analytics.logEvent(data, false, throttle, completionHandler);
});
};
@@ -16919,13 +17436,13 @@ OT.Analytics = function(loggingUrl) {
// Set a bunch of defaults
var data = OT.$.extend({
// TODO: OT.properties.version only gives '2.2', not '2.2.9.3'.
- 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''),
- 'guid' : guid,
- 'partnerId' : partnerId,
- 'source' : window.location.href,
- 'logVersion' : LOG_VERSION,
- 'clientSystemTime' : new Date().getTime(),
- 'duration' : 0 //in milliseconds
+ clientVersion: 'js-' + OT.properties.version.replace('v', ''),
+ guid: guid,
+ partnerId: partnerId,
+ source: window.location.href,
+ logVersion: LOG_VERSION,
+ clientSystemTime: new Date().getTime(),
+ duration: 0 //in milliseconds
}, options);
_analytics.logQOS(data);
@@ -16941,7 +17458,7 @@ OT.Analytics = function(loggingUrl) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-OT.ConnectivityAttemptPinger = function (options) {
+OT.ConnectivityAttemptPinger = function(options) {
var _state = 'Initial',
_previousState,
states = ['Initial', 'Attempt', 'Success', 'Failure'],
@@ -17071,7 +17588,7 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit
}
};
- if(showControls === false) {
+ if (showControls === false) {
initalStyles = {
buttonDisplayMode: 'off',
nameDisplayMode: 'off',
@@ -17086,195 +17603,181 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit
var _style = new Style(initalStyles, onStyleChange);
-/**
- * Returns an object that has the properties that define the current user interface controls of
- * the Publisher. You can modify the properties of this object and pass the object to the
- * setStyle() method of thePublisher object. (See the documentation for
- * setStyle() to see the styles that define this object.)
- * @return {Object} The object that defines the styles of the Publisher.
- * @see setStyle()
- * @method #getStyle
- * @memberOf Publisher
- */
-
-/**
- * Returns an object that has the properties that define the current user interface controls of
- * the Subscriber. You can modify the properties of this object and pass the object to the
- * setStyle() method of the Subscriber object. (See the documentation for
- * setStyle() to see the styles that define this object.)
- * @return {Object} The object that defines the styles of the Subscriber.
- * @see setStyle()
- * @method #getStyle
- * @memberOf Subscriber
- */
+ /**
+ * Returns an object that has the properties that define the current user interface controls of
+ * the Publisher. You can modify the properties of this object and pass the object to the
+ * setStyle() method of thePublisher object. (See the documentation for
+ * setStyle() to see the styles that define this object.)
+ * @return {Object} The object that defines the styles of the Publisher.
+ * @see setStyle()
+ * @method #getStyle
+ * @memberOf Publisher
+ */
+
+ /**
+ * Returns an object that has the properties that define the current user interface controls of
+ * the Subscriber. You can modify the properties of this object and pass the object to the
+ * setStyle() method of the Subscriber object. (See the documentation for
+ * setStyle() to see the styles that define this object.)
+ * @return {Object} The object that defines the styles of the Subscriber.
+ * @see setStyle()
+ * @method #getStyle
+ * @memberOf Subscriber
+ */
// If +key+ is falsly then all styles will be returned.
self.getStyle = function(key) {
return _style.get(key);
};
-/**
- * Sets properties that define the appearance of some user interface controls of the Publisher.
- *
- *
You can either pass one parameter or two parameters to this method.
- *
- *
If you pass one parameter, style, it is an object that has the following
- * properties:
- *
- *
- *
audioLevelDisplayMode (String) — How to display the audio level
- * indicator. Possible values are: "auto" (the indicator is displayed when the
- * video is disabled), "off" (the indicator is not displayed), and
- * "on" (the indicator is always displayed).
- *
- *
backgroundImageURI (String) — A URI for an image to display as
- * the background image when a video is not displayed. (A video may not be displayed if
- * you call publishVideo(false) on the Publisher object). You can pass an http
- * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
- * data URI scheme (instead of http or https) and pass in base-64-encrypted
- * PNG data, such as that obtained from the
- * Publisher.getImgData() method. For example,
- * you could set the property to "data:VBORw0KGgoAA...", where the portion of
- * the string after "data:" is the result of a call to
- * Publisher.getImgData(). If the URL or the image data is invalid, the
- * property is ignored (the attempt to set the image fails silently).
- *
- * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer),
- * you cannot set the backgroundImageURI style to a string larger than
- * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this
- * limitation, you cannot set the backgroundImageURI style to a string obtained
- * with the getImgData() method.
- *
- *
- *
buttonDisplayMode (String) — How to display the microphone
- * controls. Possible values are: "auto" (controls are displayed when the
- * stream is first displayed and when the user mouses over the display), "off"
- * (controls are not displayed), and "on" (controls are always displayed).
- *
- *
nameDisplayMode (String) Whether to display the stream name.
- * Possible values are: "auto" (the name is displayed when the stream is first
- * displayed and when the user mouses over the display), "off" (the name is not
- * displayed), and "on" (the name is always displayed).
- *
- *
- *
- *
For example, the following code passes one parameter to the method:
- *
- *
myPublisher.setStyle({nameDisplayMode: "off"});
- *
- *
If you pass two parameters, style and value, they are
- * key-value pair that define one property of the display style. For example, the following
- * code passes two parameter values to the method:
- *
- *
myPublisher.setStyle("nameDisplayMode", "off");
- *
- *
You can set the initial settings when you call the Session.publish()
- * or OT.initPublisher() method. Pass a style property as part of the
- * properties parameter of the method.
- *
- *
The OT object dispatches an exception event if you pass in an invalid style
- * to the method. The code property of the ExceptionEvent object is set to 1011.
- *
- * @param {Object} style Either an object containing properties that define the style, or a
- * String defining this single style property to set.
- * @param {String} value The value to set for the style passed in. Pass a value
- * for this parameter only if the value of the style parameter is a String.
- *
- * @see getStyle()
- * @return {Publisher} The Publisher object
- * @see setStyle()
- *
- * @see Session.publish()
- * @see OT.initPublisher()
- * @method #setStyle
- * @memberOf Publisher
- */
-
-/**
- * Sets properties that define the appearance of some user interface controls of the Subscriber.
- *
- *
You can either pass one parameter or two parameters to this method.
- *
- *
If you pass one parameter, style, it is an object that has the following
- * properties:
- *
- *
- *
audioLevelDisplayMode (String) — How to display the audio level
- * indicator. Possible values are: "auto" (the indicator is displayed when the
- * video is disabled), "off" (the indicator is not displayed), and
- * "on" (the indicator is always displayed).
- *
- *
backgroundImageURI (String) — A URI for an image to display as
- * the background image when a video is not displayed. (A video may not be displayed if
- * you call subscribeToVideo(false) on the Publisher object). You can pass an
- * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
- * data URI scheme (instead of http or https) and pass in base-64-encrypted
- * PNG data, such as that obtained from the
- * Subscriber.getImgData() method. For example,
- * you could set the property to "data:VBORw0KGgoAA...", where the portion of
- * the string after "data:" is the result of a call to
- * Publisher.getImgData(). If the URL or the image data is invalid, the
- * property is ignored (the attempt to set the image fails silently).
- *
- * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer),
- * you cannot set the backgroundImageURI style to a string larger than
- * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this
- * limitation, you cannot set the backgroundImageURI style to a string obtained
- * with the getImgData() method.
- *
- *
- *
buttonDisplayMode (String) — How to display the speaker
- * controls. Possible values are: "auto" (controls are displayed when the
- * stream is first displayed and when the user mouses over the display), "off"
- * (controls are not displayed), and "on" (controls are always displayed).
- *
- *
nameDisplayMode (String) Whether to display the stream name.
- * Possible values are: "auto" (the name is displayed when the stream is first
- * displayed and when the user mouses over the display), "off" (the name is not
- * displayed), and "on" (the name is always displayed).
- *
- *
videoDisabledDisplayMode (String) Whether to display the video
- * disabled indicator and video disabled warning icons for a Subscriber. These icons
- * indicate that the video has been disabled (or is in risk of being disabled for
- * the warning icon) due to poor stream quality. Possible values are: "auto"
- * (the icons are automatically when the displayed video is disabled or in risk of being
- * disabled due to poor stream quality), "off" (do not display the icons), and
- * "on" (display the icons).
- *
- *
- *
- *
For example, the following code passes one parameter to the method:
- *
- *
mySubscriber.setStyle({nameDisplayMode: "off"});
- *
- *
If you pass two parameters, style and value, they are key-value
- * pair that define one property of the display style. For example, the following code passes
- * two parameter values to the method:
- *
- *
mySubscriber.setStyle("nameDisplayMode", "off");
- *
- *
You can set the initial settings when you call the Session.subscribe() method.
- * Pass a style property as part of the properties parameter of the
- * method.
- *
- *
The OT object dispatches an exception event if you pass in an invalid style
- * to the method. The code property of the ExceptionEvent object is set to 1011.
- *
- * @param {Object} style Either an object containing properties that define the style, or a
- * String defining this single style property to set.
- * @param {String} value The value to set for the style passed in. Pass a value
- * for this parameter only if the value of the style parameter is a String.
- *
- * @returns {Subscriber} The Subscriber object.
- *
- * @see getStyle()
- * @see setStyle()
- *
- * @see Session.subscribe()
- * @method #setStyle
- * @memberOf Subscriber
- */
-
- if(_readOnly) {
+ /**
+ * Sets properties that define the appearance of some user interface controls of the Publisher.
+ *
+ *
You can either pass one parameter or two parameters to this method.
+ *
+ *
If you pass one parameter, style, it is an object that has the following
+ * properties:
+ *
+ *
+ *
audioLevelDisplayMode (String) — How to display the audio level
+ * indicator. Possible values are: "auto" (the indicator is displayed when the
+ * video is disabled), "off" (the indicator is not displayed), and
+ * "on" (the indicator is always displayed).
+ *
+ *
backgroundImageURI (String) — A URI for an image to display as
+ * the background image when a video is not displayed. (A video may not be displayed if
+ * you call publishVideo(false) on the Publisher object). You can pass an http
+ * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
+ * data URI scheme (instead of http or https) and pass in base-64-encrypted
+ * PNG data, such as that obtained from the
+ * Publisher.getImgData() method. For example,
+ * you could set the property to "data:VBORw0KGgoAA...", where the portion of
+ * the string after "data:" is the result of a call to
+ * Publisher.getImgData(). If the URL or the image data is invalid, the
+ * property is ignored (the attempt to set the image fails silently).
+ *
+ *
buttonDisplayMode (String) — How to display the microphone
+ * controls. Possible values are: "auto" (controls are displayed when the
+ * stream is first displayed and when the user mouses over the display), "off"
+ * (controls are not displayed), and "on" (controls are always displayed).
+ *
+ *
nameDisplayMode (String) Whether to display the stream name.
+ * Possible values are: "auto" (the name is displayed when the stream is first
+ * displayed and when the user mouses over the display), "off" (the name is not
+ * displayed), and "on" (the name is always displayed).
+ *
+ *
+ *
+ *
For example, the following code passes one parameter to the method:
+ *
+ *
myPublisher.setStyle({nameDisplayMode: "off"});
+ *
+ *
If you pass two parameters, style and value, they are
+ * key-value pair that define one property of the display style. For example, the following
+ * code passes two parameter values to the method:
+ *
+ *
myPublisher.setStyle("nameDisplayMode", "off");
+ *
+ *
You can set the initial settings when you call the Session.publish()
+ * or OT.initPublisher() method. Pass a style property as part of the
+ * properties parameter of the method.
+ *
+ *
The OT object dispatches an exception event if you pass in an invalid style
+ * to the method. The code property of the ExceptionEvent object is set to 1011.
+ *
+ * @param {Object} style Either an object containing properties that define the style, or a
+ * String defining this single style property to set.
+ * @param {String} value The value to set for the style passed in. Pass a value
+ * for this parameter only if the value of the style parameter is a String.
+ *
+ * @see getStyle()
+ * @return {Publisher} The Publisher object
+ * @see setStyle()
+ *
+ * @see Session.publish()
+ * @see OT.initPublisher()
+ * @method #setStyle
+ * @memberOf Publisher
+ */
+
+ /**
+ * Sets properties that define the appearance of some user interface controls of the Subscriber.
+ *
+ *
You can either pass one parameter or two parameters to this method.
+ *
+ *
If you pass one parameter, style, it is an object that has the following
+ * properties:
+ *
+ *
+ *
audioLevelDisplayMode (String) — How to display the audio level
+ * indicator. Possible values are: "auto" (the indicator is displayed when the
+ * video is disabled), "off" (the indicator is not displayed), and
+ * "on" (the indicator is always displayed).
+ *
+ *
backgroundImageURI (String) — A URI for an image to display as
+ * the background image when a video is not displayed. (A video may not be displayed if
+ * you call subscribeToVideo(false) on the Publisher object). You can pass an
+ * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
+ * data URI scheme (instead of http or https) and pass in base-64-encrypted
+ * PNG data, such as that obtained from the
+ * Subscriber.getImgData() method. For example,
+ * you could set the property to "data:VBORw0KGgoAA...", where the portion of
+ * the string after "data:" is the result of a call to
+ * Publisher.getImgData(). If the URL or the image data is invalid, the
+ * property is ignored (the attempt to set the image fails silently).
+ *
+ *
buttonDisplayMode (String) — How to display the speaker
+ * controls. Possible values are: "auto" (controls are displayed when the
+ * stream is first displayed and when the user mouses over the display), "off"
+ * (controls are not displayed), and "on" (controls are always displayed).
+ *
+ *
nameDisplayMode (String) Whether to display the stream name.
+ * Possible values are: "auto" (the name is displayed when the stream is first
+ * displayed and when the user mouses over the display), "off" (the name is not
+ * displayed), and "on" (the name is always displayed).
+ *
+ *
videoDisabledDisplayMode (String) Whether to display the video
+ * disabled indicator and video disabled warning icons for a Subscriber. These icons
+ * indicate that the video has been disabled (or is in risk of being disabled for
+ * the warning icon) due to poor stream quality. Possible values are: "auto"
+ * (the icons are automatically when the displayed video is disabled or in risk of being
+ * disabled due to poor stream quality), "off" (do not display the icons), and
+ * "on" (display the icons).
+ *
+ *
+ *
+ *
For example, the following code passes one parameter to the method:
+ *
+ *
mySubscriber.setStyle({nameDisplayMode: "off"});
+ *
+ *
If you pass two parameters, style and value, they are key-value
+ * pair that define one property of the display style. For example, the following code passes
+ * two parameter values to the method:
+ *
+ *
mySubscriber.setStyle("nameDisplayMode", "off");
+ *
+ *
You can set the initial settings when you call the Session.subscribe() method.
+ * Pass a style property as part of the properties parameter of the
+ * method.
+ *
+ *
The OT object dispatches an exception event if you pass in an invalid style
+ * to the method. The code property of the ExceptionEvent object is set to 1011.
+ *
+ * @param {Object} style Either an object containing properties that define the style, or a
+ * String defining this single style property to set.
+ * @param {String} value The value to set for the style passed in. Pass a value
+ * for this parameter only if the value of the style parameter is a String.
+ *
+ * @returns {Subscriber} The Subscriber object.
+ *
+ * @see getStyle()
+ * @see setStyle()
+ *
+ * @see Session.subscribe()
+ * @method #setStyle
+ * @memberOf Subscriber
+ */
+
+ if (_readOnly) {
self.setStyle = function() {
OT.warn('Calling setStyle() has no effect because the' +
'showControls option was set to false');
@@ -17283,7 +17786,7 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit
} else {
self.setStyle = function(keyOrStyleHash, value, silent) {
var logPayload = {};
- if (typeof(keyOrStyleHash) !== 'string') {
+ if (typeof keyOrStyleHash !== 'string') {
_style.setAll(keyOrStyleHash, silent);
logPayload = keyOrStyleHash;
} else {
@@ -17296,17 +17799,15 @@ OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWit
}
};
-
/*jshint latedef:false */
var Style = function(initalStyles, onStyleChange) {
-/*jshint latedef:true */
+ /*jshint latedef:true */
var _style = {},
_COMPONENT_STYLES,
_validStyleValues,
isValidStyle,
castValue;
-
_COMPONENT_STYLES = [
'showMicButton',
'showSpeakerButton',
@@ -17332,11 +17833,11 @@ var Style = function(initalStyles, onStyleChange) {
isValidStyle = function(key, value) {
return key === 'backgroundImageURI' ||
(_validStyleValues.hasOwnProperty(key) &&
- OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1 );
+ OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1);
};
castValue = function(value) {
- switch(value) {
+ switch (value) {
case 'true':
return true;
case 'false':
@@ -17351,7 +17852,7 @@ var Style = function(initalStyles, onStyleChange) {
var style = OT.$.clone(_style);
for (var key in style) {
- if(!style.hasOwnProperty(key)) {
+ if (!style.hasOwnProperty(key)) {
continue;
}
if (OT.$.arrayIndexOf(_COMPONENT_STYLES, key) < 0) {
@@ -17378,7 +17879,7 @@ var Style = function(initalStyles, onStyleChange) {
var oldValue, newValue;
for (var key in newStyles) {
- if(!newStyles.hasOwnProperty(key)) {
+ if (!newStyles.hasOwnProperty(key)) {
continue;
}
newValue = castValue(newStyles[key]);
@@ -17429,7 +17930,6 @@ var Style = function(initalStyles, onStyleChange) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
// A Factory method for generating simple state machine classes.
//
// @usage
@@ -17447,7 +17947,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
var validStates = states.slice(),
validTransitions = OT.$.clone(transitions);
- var isValidState = function (state) {
+ var isValidState = function(state) {
return OT.$.arrayIndexOf(validStates, state) !== -1;
};
@@ -17489,7 +17989,6 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
return true;
}
-
this.set = function(newState) {
if (!handleInvalidStateChanges(newState)) return;
previousState = currentState;
@@ -17508,63 +18007,63 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
!(function() {
-// Models a Subscriber's subscribing State
-//
-// Valid States:
-// NotSubscribing (the initial state
-// Init (basic setup of DOM
-// ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer
-// BindingRemoteStream (Failure Cases -> Anything to do with the media being
-// (invalid, the media never plays
-// Subscribing (this is 'onLoad'
-// Failed (terminal state, with a reason that maps to one of the
-// (failure cases above
-// Destroyed (The subscriber has been cleaned up, terminal state
-//
-//
-// Valid Transitions:
-// NotSubscribing ->
-// Init
-//
-// Init ->
-// ConnectingToPeer
-// | BindingRemoteStream (if we are subscribing to ourselves and we alreay
-// (have a stream
-// | NotSubscribing (destroy()
-//
-// ConnectingToPeer ->
-// BindingRemoteStream
-// | NotSubscribing
-// | Failed
-// | NotSubscribing (destroy()
-//
-// BindingRemoteStream ->
-// Subscribing
-// | Failed
-// | NotSubscribing (destroy()
-//
-// Subscribing ->
-// NotSubscribing (unsubscribe
-// | Failed (probably a peer connection failure after we began
-// (subscribing
-//
-// Failed ->
-// Destroyed
-//
-// Destroyed -> (terminal state)
-//
-//
-// @example
-// var state = new SubscribingState(function(change) {
-// console.log(change.message);
-// });
-//
-// state.set('Init');
-// state.current; -> 'Init'
-//
-// state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message
-//
-//
+ // Models a Subscriber's subscribing State
+ //
+ // Valid States:
+ // NotSubscribing (the initial state
+ // Init (basic setup of DOM
+ // ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer
+ // BindingRemoteStream (Failure Cases -> Anything to do with the media being
+ // (invalid, the media never plays
+ // Subscribing (this is 'onLoad'
+ // Failed (terminal state, with a reason that maps to one of the
+ // (failure cases above
+ // Destroyed (The subscriber has been cleaned up, terminal state
+ //
+ //
+ // Valid Transitions:
+ // NotSubscribing ->
+ // Init
+ //
+ // Init ->
+ // ConnectingToPeer
+ // | BindingRemoteStream (if we are subscribing to ourselves and we alreay
+ // (have a stream
+ // | NotSubscribing (destroy()
+ //
+ // ConnectingToPeer ->
+ // BindingRemoteStream
+ // | NotSubscribing
+ // | Failed
+ // | NotSubscribing (destroy()
+ //
+ // BindingRemoteStream ->
+ // Subscribing
+ // | Failed
+ // | NotSubscribing (destroy()
+ //
+ // Subscribing ->
+ // NotSubscribing (unsubscribe
+ // | Failed (probably a peer connection failure after we began
+ // (subscribing
+ //
+ // Failed ->
+ // Destroyed
+ //
+ // Destroyed -> (terminal state)
+ //
+ //
+ // @example
+ // var state = new SubscribingState(function(change) {
+ // console.log(change.message);
+ // });
+ //
+ // state.set('Init');
+ // state.current; -> 'Init'
+ //
+ // state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message
+ //
+ //
var validStates,
validTransitions,
initialState = 'NotSubscribing';
@@ -17601,7 +18100,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
OT.SubscribingState.prototype.isAttemptingToSubscribe = function() {
return OT.$.arrayIndexOf(
- [ 'Init', 'ConnectingToPeer', 'BindingRemoteStream' ],
+ ['Init', 'ConnectingToPeer', 'BindingRemoteStream'],
this.current
) !== -1;
};
@@ -17617,62 +18116,62 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
!(function() {
-// Models a Publisher's publishing State
-//
-// Valid States:
-// NotPublishing
-// GetUserMedia
-// BindingMedia
-// MediaBound
-// PublishingToSession
-// Publishing
-// Failed
-// Destroyed
-//
-//
-// Valid Transitions:
-// NotPublishing ->
-// GetUserMedia
-//
-// GetUserMedia ->
-// BindingMedia
-// | Failed (Failure Reasons -> stream error, constraints,
-// (permission denied
-// | NotPublishing (destroy()
-//
-//
-// BindingMedia ->
-// MediaBound
-// | Failed (Failure Reasons -> Anything to do with the media
-// (being invalid, the media never plays
-// | NotPublishing (destroy()
-//
-// MediaBound ->
-// PublishingToSession (MediaBound could transition to PublishingToSession
-// (if a stand-alone publish is bound to a session
-// | Failed (Failure Reasons -> media issues with a stand-alone publisher
-// | NotPublishing (destroy()
-//
-// PublishingToSession
-// Publishing
-// | Failed (Failure Reasons -> timeout while waiting for ack of
-// (stream registered. We do not do this right now
-// | NotPublishing (destroy()
-//
-//
-// Publishing ->
-// NotPublishing (Unpublish
-// | Failed (Failure Reasons -> loss of network, media error, anything
-// (that causes *all* Peer Connections to fail (less than all
-// (failing is just an error, all is failure)
-// | NotPublishing (destroy()
-//
-// Failed ->
-// Destroyed
-//
-// Destroyed -> (Terminal state
-//
-//
+ // Models a Publisher's publishing State
+ //
+ // Valid States:
+ // NotPublishing
+ // GetUserMedia
+ // BindingMedia
+ // MediaBound
+ // PublishingToSession
+ // Publishing
+ // Failed
+ // Destroyed
+ //
+ //
+ // Valid Transitions:
+ // NotPublishing ->
+ // GetUserMedia
+ //
+ // GetUserMedia ->
+ // BindingMedia
+ // | Failed (Failure Reasons -> stream error, constraints,
+ // (permission denied
+ // | NotPublishing (destroy()
+ //
+ //
+ // BindingMedia ->
+ // MediaBound
+ // | Failed (Failure Reasons -> Anything to do with the media
+ // (being invalid, the media never plays
+ // | NotPublishing (destroy()
+ //
+ // MediaBound ->
+ // PublishingToSession (MediaBound could transition to PublishingToSession
+ // (if a stand-alone publish is bound to a session
+ // | Failed (Failure Reasons -> media issues with a stand-alone publisher
+ // | NotPublishing (destroy()
+ //
+ // PublishingToSession
+ // Publishing
+ // | Failed (Failure Reasons -> timeout while waiting for ack of
+ // (stream registered. We do not do this right now
+ // | NotPublishing (destroy()
+ //
+ //
+ // Publishing ->
+ // NotPublishing (Unpublish
+ // | Failed (Failure Reasons -> loss of network, media error, anything
+ // (that causes *all* Peer Connections to fail (less than all
+ // (failing is just an error, all is failure)
+ // | NotPublishing (destroy()
+ //
+ // Failed ->
+ // Destroyed
+ //
+ // Destroyed -> (Terminal state
+ //
+ //
var validStates = [
'NotPublishing', 'GetUserMedia', 'BindingMedia', 'MediaBound',
@@ -17701,7 +18200,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
OT.PublishingState.prototype.isAttemptingToPublish = function() {
return OT.$.arrayIndexOf(
- [ 'GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession' ],
+ ['GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession'],
this.current) !== -1;
};
@@ -17716,16 +18215,16 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
!(function() {
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OT */
+ /* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+ /* global OT */
-/*
- * A Publishers Microphone.
- *
- * TODO
- * * bind to changes in mute/unmute/volume/etc and respond to them
- */
+ /*
+ * A Publishers Microphone.
+ *
+ * TODO
+ * * bind to changes in mute/unmute/volume/etc and respond to them
+ */
OT.Microphone = function(webRTCStream, muted) {
var _muted;
@@ -17741,7 +18240,7 @@ OT.generateSimpleStateMachine = function(initialState, states, transitions) {
var audioTracks = webRTCStream.getAudioTracks();
- for (var i=0, num=audioTracks.length; iwindow.setInterval.
- *
- * @param {function()} callback
- * @param {number} frequency how many times per second we want to execute the callback
- * @constructor
- */
-OT.IntervalRunner = function(callback, frequency) {
- var _callback = callback,
- _frequency = frequency,
- _intervalId = null;
-
- this.start = function() {
- _intervalId = window.setInterval(_callback, 1000 / _frequency);
- };
-
- this.stop = function() {
- window.clearInterval(_intervalId);
- _intervalId = null;
- };
-};
-
-// tb_require('../helpers/helpers.js')
-
!(function() {
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
@@ -17888,7 +18359,6 @@ OT.IntervalRunner = function(callback, frequency) {
STREAM_PROPERTY_CHANGED: 'streamPropertyChanged',
MICROPHONE_LEVEL_CHANGED: 'microphoneLevelChanged',
-
// Publisher Events
RESIZE: 'resize',
SETTINGS_BUTTON_CLICK: 'settingsButtonClick',
@@ -17912,8 +18382,8 @@ OT.IntervalRunner = function(callback, frequency) {
DEVICES_SELECTED: 'devicesSelected',
CLOSE_BUTTON_CLICK: 'closeButtonClick',
- MICLEVEL : 'microphoneActivityLevel',
- MICGAINCHANGED : 'microphoneGainChanged',
+ MICLEVEL: 'microphoneActivityLevel',
+ MICGAINCHANGED: 'microphoneGainChanged',
// Environment Loader
ENV_LOADED: 'envLoaded',
@@ -17931,6 +18401,7 @@ OT.IntervalRunner = function(callback, frequency) {
CONNECT_REJECTED: 1007,
CONNECTION_TIMEOUT: 1008,
NOT_CONNECTED: 1010,
+ INVALID_PARAMETER: 1011,
P2P_CONNECTION_FAILED: 1013,
API_RESPONSE_FAILURE: 1014,
TERMS_OF_SERVICE_FAILURE: 1026,
@@ -17940,7 +18411,8 @@ OT.IntervalRunner = function(callback, frequency) {
UNABLE_TO_FORCE_UNPUBLISH: 1530,
PUBLISHER_ICE_WORKFLOW_FAILED: 1553,
SUBSCRIBER_ICE_WORKFLOW_FAILED: 1554,
- UNEXPECTED_SERVER_RESPONSE: 2001
+ UNEXPECTED_SERVER_RESPONSE: 2001,
+ REPORT_ISSUE_ERROR: 2011
};
/**
@@ -18131,7 +18603,7 @@ OT.IntervalRunner = function(callback, frequency) {
* @property {String} title The error title.
* @augments Event
*/
- OT.ExceptionEvent = function (type, message, title, code, component, target) {
+ OT.ExceptionEvent = function(type, message, title, code, component, target) {
OT.Event.call(this, type);
this.message = message;
@@ -18141,106 +18613,104 @@ OT.IntervalRunner = function(callback, frequency) {
this.target = target;
};
-
- OT.IssueReportedEvent = function (type, issueId) {
+ OT.IssueReportedEvent = function(type, issueId) {
OT.Event.call(this, type);
this.issueId = issueId;
};
// Triggered when the JS dynamic config and the DOM have loaded.
- OT.EnvLoadedEvent = function (type) {
+ OT.EnvLoadedEvent = function(type) {
OT.Event.call(this, type);
};
-
-/**
- * Defines connectionCreated and connectionDestroyed events dispatched by
- * the {@link Session} object.
- *
- * The Session object dispatches a connectionCreated event when a client (including
- * your own) connects to a Session. It also dispatches a connectionCreated event for
- * every client in the session when you first connect. (when your local client connects, the Session
- * object also dispatches a sessionConnected event, defined by the
- * {@link SessionConnectEvent} class.)
- *
- * While you are connected to the session, the Session object dispatches a
- * connectionDestroyed event when another client disconnects from the Session.
- * (When you disconnect, the Session object also dispatches a sessionDisconnected
- * event, defined by the {@link SessionDisconnectEvent} class.)
- *
- *
Example
- *
- *
The following code keeps a running total of the number of connections to a session
- * by monitoring the connections property of the sessionConnect,
- * connectionCreated and connectionDestroyed events:
- *
- *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- * var sessionID = ""; // Replace with your own session ID.
- * // See https://dashboard.tokbox.com/projects
- * var token = ""; // Replace with a generated token that has been assigned the moderator role.
- * // See https://dashboard.tokbox.com/projects
- * var connectionCount = 0;
- *
- * var session = OT.initSession(apiKey, sessionID);
- * session.on("connectionCreated", function(event) {
- * connectionCount++;
- * displayConnectionCount();
- * });
- * session.on("connectionDestroyed", function(event) {
- * connectionCount--;
- * displayConnectionCount();
- * });
- * session.connect(token);
- *
- * function displayConnectionCount() {
- * document.getElementById("connectionCountField").value = connectionCount.toString();
- * }
- *
- *
This example assumes that there is an input text field in the HTML DOM
- * with the id set to "connectionCountField":
- *
- *
- * @property {Connection} connection A Connection objects for the connections that was
- * created or deleted.
- *
- * @property {Array} connections Deprecated. Use the connection property. A
- * connectionCreated or connectionDestroyed event is dispatched
- * for each connection created and destroyed in the session.
- *
- * @property {String} reason For a connectionDestroyed event,
- * a description of why the connection ended. This property can have two values:
- *
- *
- *
"clientDisconnected" A client disconnected from the session by calling
- * the disconnect() method of the Session object or by closing the browser.
- * (See Session.disconnect().)
- *
- *
"forceDisconnected" A moderator has disconnected the publisher
- * from the session, by calling the forceDisconnect() method of the Session
- * object. (See Session.forceDisconnect().)
- *
- *
"networkDisconnected" The network connection terminated abruptly
- * (for example, the client lost their internet connection).
- *
- *
- *
Depending on the context, this description may allow the developer to refine
- * the course of action they take in response to an event.
- *
- *
For a connectionCreated event, this string is undefined.
+ * The Session object dispatches a connectionCreated event when a client (including
+ * your own) connects to a Session. It also dispatches a connectionCreated event for
+ * every client in the session when you first connect. (when your local client connects, the
+ * Session object also dispatches a sessionConnected event, defined by the
+ * {@link SessionConnectEvent} class.)
+ *
+ * While you are connected to the session, the Session object dispatches a
+ * connectionDestroyed event when another client disconnects from the Session.
+ * (When you disconnect, the Session object also dispatches a sessionDisconnected
+ * event, defined by the {@link SessionDisconnectEvent} class.)
+ *
+ *
Example
+ *
+ *
The following code keeps a running total of the number of connections to a session
+ * by monitoring the connections property of the sessionConnect,
+ * connectionCreated and connectionDestroyed events:
+ *
+ *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ * // See https://dashboard.tokbox.com/projects
+ * var connectionCount = 0;
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("connectionCreated", function(event) {
+ * connectionCount++;
+ * displayConnectionCount();
+ * });
+ * session.on("connectionDestroyed", function(event) {
+ * connectionCount--;
+ * displayConnectionCount();
+ * });
+ * session.connect(token);
+ *
+ * function displayConnectionCount() {
+ * document.getElementById("connectionCountField").value = connectionCount.toString();
+ * }
+ *
+ *
This example assumes that there is an input text field in the HTML DOM
+ * with the id set to "connectionCountField":
+ *
+ *
+ * @property {Connection} connection A Connection objects for the connections that was
+ * created or deleted.
+ *
+ * @property {Array} connections Deprecated. Use the connection property. A
+ * connectionCreated or connectionDestroyed event is dispatched
+ * for each connection created and destroyed in the session.
+ *
+ * @property {String} reason For a connectionDestroyed event,
+ * a description of why the connection ended. This property can have two values:
+ *
+ *
+ *
"clientDisconnected" A client disconnected from the session by calling
+ * the disconnect() method of the Session object or by closing the browser.
+ * (See Session.disconnect().)
+ *
+ *
"forceDisconnected" A moderator has disconnected the publisher
+ * from the session, by calling the forceDisconnect() method of the Session
+ * object. (See Session.forceDisconnect().)
+ *
+ *
"networkDisconnected" The network connection terminated abruptly
+ * (for example, the client lost their internet connection).
+ *
+ *
+ *
Depending on the context, this description may allow the developer to refine
+ * the course of action they take in response to an event.
+ *
+ *
For a connectionCreated event, this string is undefined.
+ *
+ * @class ConnectionEvent
+ * @augments Event
+ */
var connectionEventPluralDeprecationWarningShown = false;
- OT.ConnectionEvent = function (type, connection, reason) {
+ OT.ConnectionEvent = function(type, connection, reason) {
OT.Event.call(this, type, false);
if (OT.$.canDefineProperty) {
Object.defineProperty(this, 'connections', {
get: function() {
- if(!connectionEventPluralDeprecationWarningShown) {
+ if (!connectionEventPluralDeprecationWarningShown) {
OT.warn('OT.ConnectionEvent connections property is deprecated, ' +
'use connection instead.');
connectionEventPluralDeprecationWarningShown = true;
@@ -18256,119 +18726,120 @@ OT.IntervalRunner = function(callback, frequency) {
this.reason = reason;
};
-/**
- * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed".
- * These events are dispatched by the Session object when another client starts or
- * stops publishing a stream to a {@link Session}. For a local client's stream, the
- * Publisher object dispatches the event.
- *
- *
Example streamCreated event dispatched
- * by the Session object
- *
The following code initializes a session and sets up an event listener for when
- * a stream published by another client is created:
- *
- *
- * session.on("streamCreated", function(event) {
- * // streamContainer is a DOM element
- * subscriber = session.subscribe(event.stream, targetElement);
- * }).connect(token);
- *
- *
- *
Example streamDestroyed event dispatched
- * by the Session object
- *
- *
The following code initializes a session and sets up an event listener for when
- * other clients' streams end:
- *
- * @class StreamEvent
- *
- * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable
- * (true) or not (false). You can cancel the default behavior by calling
- * the preventDefault() method of the StreamEvent object in the event listener
- * function. The streamDestroyed
- * event is cancelable. (See preventDefault().)
- *
- * @property {String} reason For a streamDestroyed event,
- * a description of why the session disconnected. This property can have one of the following
- * values:
- *
- *
- *
"clientDisconnected" A client disconnected from the session by calling
- * the disconnect() method of the Session object or by closing the browser.
- * (See Session.disconnect().)
- *
- *
"forceDisconnected" A moderator has disconnected the publisher of the
- * stream from the session, by calling the forceDisconnect() method of the Session
-* object. (See Session.forceDisconnect().)
- *
- *
"forceUnpublished" A moderator has forced the publisher of the stream
- * to stop publishing the stream, by calling the forceUnpublish() method of the
- * Session object. (See Session.forceUnpublish().)
- *
- *
"mediaStopped" The user publishing the stream has stopped sharing the
- * screen. This value is only used in screen-sharing video streams.
- *
- *
"networkDisconnected" The network connection terminated abruptly (for
- * example, the client lost their internet connection).
- *
- *
- *
- *
Depending on the context, this description may allow the developer to refine
- * the course of action they take in response to an event.
- *
- *
For a streamCreated event, this string is undefined.
- *
- * @property {Stream} stream A Stream object corresponding to the stream that was added (in the
- * case of a streamCreated event) or deleted (in the case of a
- * streamDestroyed event).
- *
- * @property {Array} streams Deprecated. Use the stream property. A
- * streamCreated or streamDestroyed event is dispatched for
- * each stream added or destroyed.
- *
- * @augments Event
- */
+ /**
+ * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed".
+ * These events are dispatched by the Session object when another client starts or
+ * stops publishing a stream to a {@link Session}. For a local client's stream, the
+ * Publisher object dispatches the event.
+ *
+ *
Example streamCreated event dispatched
+ * by the Session object
+ *
The following code initializes a session and sets up an event listener for when
+ * a stream published by another client is created:
+ *
+ *
+ * session.on("streamCreated", function(event) {
+ * // streamContainer is a DOM element
+ * subscriber = session.subscribe(event.stream, targetElement);
+ * }).connect(token);
+ *
+ *
+ *
Example streamDestroyed event dispatched
+ * by the Session object
+ *
+ *
The following code initializes a session and sets up an event listener for when
+ * other clients' streams end:
+ *
+ * @class StreamEvent
+ *
+ * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable
+ * (true) or not (false). You can cancel the default behavior by
+ * calling the preventDefault() method of the StreamEvent object in the event
+ * listener function. The streamDestroyed event is cancelable.
+ * (See preventDefault().)
+ *
+ * @property {String} reason For a streamDestroyed event,
+ * a description of why the session disconnected. This property can have one of the following
+ * values:
+ *
+ *
+ *
"clientDisconnected" A client disconnected from the session by calling
+ * the disconnect() method of the Session object or by closing the browser.
+ * (See Session.disconnect().)
+ *
+ *
"forceDisconnected" A moderator has disconnected the publisher of the
+ * stream from the session, by calling the forceDisconnect() method of the Session
+ * object. (See Session.forceDisconnect().)
+ *
+ *
"forceUnpublished" A moderator has forced the publisher of the stream
+ * to stop publishing the stream, by calling the forceUnpublish() method of the
+ * Session object.
+ * (See Session.forceUnpublish().)
+ *
+ *
"mediaStopped" The user publishing the stream has stopped sharing the
+ * screen. This value is only used in screen-sharing video streams.
+ *
+ *
"networkDisconnected" The network connection terminated abruptly (for
+ * example, the client lost their internet connection).
+ *
+ *
+ *
+ *
Depending on the context, this description may allow the developer to refine
+ * the course of action they take in response to an event.
+ *
+ *
For a streamCreated event, this string is undefined.
+ *
+ * @property {Stream} stream A Stream object corresponding to the stream that was added (in the
+ * case of a streamCreated event) or deleted (in the case of a
+ * streamDestroyed event).
+ *
+ * @property {Array} streams Deprecated. Use the stream property. A
+ * streamCreated or streamDestroyed event is dispatched for
+ * each stream added or destroyed.
+ *
+ * @augments Event
+ */
var streamEventPluralDeprecationWarningShown = false;
- OT.StreamEvent = function (type, stream, reason, cancelable) {
+ OT.StreamEvent = function(type, stream, reason, cancelable) {
OT.Event.call(this, type, cancelable);
if (OT.$.canDefineProperty) {
Object.defineProperty(this, 'streams', {
get: function() {
- if(!streamEventPluralDeprecationWarningShown) {
+ if (!streamEventPluralDeprecationWarningShown) {
OT.warn('OT.StreamEvent streams property is deprecated, use stream instead.');
streamEventPluralDeprecationWarningShown = true;
}
@@ -18383,69 +18854,71 @@ OT.IntervalRunner = function(callback, frequency) {
this.reason = reason;
};
-/**
-* Prevents the default behavior associated with the event from taking place.
-*
-*
For the streamDestroyed event dispatched by the Session object,
-* the default behavior is that all Subscriber objects that are subscribed to the stream are
-* unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a
-* destroyed event when the element is removed from the HTML DOM. If you call the
-* preventDefault() method in the event listener for the streamDestroyed
-* event, the default behavior is prevented and you can clean up Subscriber objects using your
-* own code. See
-* Session.getSubscribersForStream().
-*
-* For the streamDestroyed event dispatched by a Publisher object, the default
-* behavior is that the Publisher object is removed from the HTML DOM. The Publisher object
-* dispatches a destroyed event when the element is removed from the HTML DOM.
-* If you call the preventDefault() method in the event listener for the
-* streamDestroyed event, the default behavior is prevented, and you can
-* retain the Publisher for reuse or clean it up using your own code.
-*
-*
To see whether an event has a default behavior, check the cancelable property of
-* the event object.
-*
-*
Call the preventDefault() method in the event listener function for the event.
-*
-* @method #preventDefault
-* @memberof StreamEvent
+ /**
+ * Prevents the default behavior associated with the event from taking place.
+ *
+ *
For the streamDestroyed event dispatched by the Session object,
+ * the default behavior is that all Subscriber objects that are subscribed to the stream are
+ * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a
+ * destroyed event when the element is removed from the HTML DOM. If you call the
+ * preventDefault() method in the event listener for the streamDestroyed
+ * event, the default behavior is prevented and you can clean up Subscriber objects using your
+ * own code. See
+ * Session.getSubscribersForStream().
+ *
+ * For the streamDestroyed event dispatched by a Publisher object, the default
+ * behavior is that the Publisher object is removed from the HTML DOM. The Publisher object
+ * dispatches a destroyed event when the element is removed from the HTML DOM.
+ * If you call the preventDefault() method in the event listener for the
+ * streamDestroyed event, the default behavior is prevented, and you can
+ * retain the Publisher for reuse or clean it up using your own code.
+ *
+ *
To see whether an event has a default behavior, check the cancelable property of
+ * the event object.
+ *
+ *
+ * Call the preventDefault() method in the event listener function for the event.
+ *
+ *
+ * @method #preventDefault
+ * @memberof StreamEvent
*/
-/**
- * The Session object dispatches SessionConnectEvent object when a session has successfully
- * connected in response to a call to the connect() method of the Session object.
- *
- * In version 2.2, the completionHandler of the Session.connect() method
- * indicates success or failure in connecting to the session.
- *
- * @class SessionConnectEvent
- * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In
- * version 2.2, listen for the connectionCreated event dispatched by the Session
- * object. In version 2.2, the Session object dispatches a connectionCreated event
- * for each connection (including your own). This includes connections present when you first
- * connect to the session.
- *
- * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version
- * 2.2, listen for the streamCreated event dispatched by the Session object. In
- * version 2.2, the Session object dispatches a streamCreated event for each stream
- * other than those published by your client. This includes streams
- * present when you first connect to the session.
- *
- * @see Session.connect()
- * @augments Event
- */
+ /**
+ * The Session object dispatches SessionConnectEvent object when a session has successfully
+ * connected in response to a call to the connect() method of the Session object.
+ *
+ * In version 2.2, the completionHandler of the Session.connect() method
+ * indicates success or failure in connecting to the session.
+ *
+ * @class SessionConnectEvent
+ * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In
+ * version 2.2, listen for the connectionCreated event dispatched by the Session
+ * object. In version 2.2, the Session object dispatches a connectionCreated event
+ * for each connection (including your own). This includes connections present when you first
+ * connect to the session.
+ *
+ * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version
+ * 2.2, listen for the streamCreated event dispatched by the Session object. In
+ * version 2.2, the Session object dispatches a streamCreated event for each stream
+ * other than those published by your client. This includes streams
+ * present when you first connect to the session.
+ *
+ * @see Session.connect()
+ * @augments Event
+ */
var sessionConnectedConnectionsDeprecationWarningShown = false;
var sessionConnectedStreamsDeprecationWarningShown = false;
var sessionConnectedArchivesDeprecationWarningShown = false;
- OT.SessionConnectEvent = function (type) {
+ OT.SessionConnectEvent = function(type) {
OT.Event.call(this, type, false);
if (OT.$.canDefineProperty) {
Object.defineProperties(this, {
connections: {
get: function() {
- if(!sessionConnectedConnectionsDeprecationWarningShown) {
+ if (!sessionConnectedConnectionsDeprecationWarningShown) {
OT.warn('OT.SessionConnectedEvent no longer includes connections. Listen ' +
'for connectionCreated events instead.');
sessionConnectedConnectionsDeprecationWarningShown = true;
@@ -18455,7 +18928,7 @@ OT.IntervalRunner = function(callback, frequency) {
},
streams: {
get: function() {
- if(!sessionConnectedStreamsDeprecationWarningShown) {
+ if (!sessionConnectedStreamsDeprecationWarningShown) {
OT.warn('OT.SessionConnectedEvent no longer includes streams. Listen for ' +
'streamCreated events instead.');
sessionConnectedConnectionsDeprecationWarningShown = true;
@@ -18465,7 +18938,7 @@ OT.IntervalRunner = function(callback, frequency) {
},
archives: {
get: function() {
- if(!sessionConnectedArchivesDeprecationWarningShown) {
+ if (!sessionConnectedArchivesDeprecationWarningShown) {
OT.warn('OT.SessionConnectedEvent no longer includes archives. Listen for ' +
'archiveStarted events instead.');
sessionConnectedArchivesDeprecationWarningShown = true;
@@ -18481,109 +18954,124 @@ OT.IntervalRunner = function(callback, frequency) {
}
};
-/**
- * The Session object dispatches SessionDisconnectEvent object when a session has disconnected.
- * This event may be dispatched asynchronously in response to a successful call to the
- * disconnect() method of the session object.
- *
- *
- * Example
- *
- *
- * The following code initializes a session and sets up an event listener for when a session is
- * disconnected.
- *
- *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- * var sessionID = ""; // Replace with your own session ID.
- * // See https://dashboard.tokbox.com/projects
- * var token = ""; // Replace with a generated token that has been assigned the moderator role.
- * // See https://dashboard.tokbox.com/projects
- *
- * var session = OT.initSession(apiKey, sessionID);
- * session.on("sessionDisconnected", function(event) {
- * alert("The session disconnected. " + event.reason);
- * });
- * session.connect(token);
- *
- *
- * @property {String} reason A description of why the session disconnected.
- * This property can have two values:
- *
- *
- *
"clientDisconnected" A client disconnected from the session by calling
- * the disconnect() method of the Session object or by closing the browser.
- * ( See Session.disconnect().)
- *
"forceDisconnected" A moderator has disconnected you from the session
- * by calling the forceDisconnect() method of the Session object. (See
- * Session.forceDisconnect().)
- *
"networkDisconnected" The network connection terminated abruptly
- * (for example, the client lost their internet connection).
- *
- *
- * @class SessionDisconnectEvent
- * @augments Event
- */
- OT.SessionDisconnectEvent = function (type, reason, cancelable) {
+ /**
+ * The Session object dispatches SessionDisconnectEvent object when a session has disconnected.
+ * This event may be dispatched asynchronously in response to a successful call to the
+ * disconnect() method of the session object.
+ *
+ *
+ * Example
+ *
+ *
+ * The following code initializes a session and sets up an event listener for when a session is
+ * disconnected.
+ *
+ *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ * // See https://dashboard.tokbox.com/projects
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("sessionDisconnected", function(event) {
+ * alert("The session disconnected. " + event.reason);
+ * });
+ * session.connect(token);
+ *
+ *
+ * @property {String} reason A description of why the session disconnected.
+ * This property can have two values:
+ *
+ *
+ *
+ * "clientDisconnected" — A client disconnected from the
+ * session by calling the disconnect() method of the Session
+ * object or by closing the browser. ( See Session.disconnect().)
+ *
+ *
+ *
+ * "forceDisconnected" — A moderator has disconnected you from
+ * the session by calling the forceDisconnect() method of the
+ * Session object. (See Session.forceDisconnect().)
+ *
+ *
+ *
"networkDisconnected" — The network connection terminated
+ * abruptly (for example, the client lost their internet connection).
For the sessionDisconnectEvent, the default behavior is that all Subscriber
-* objects are unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a
-* destroyed event when the element is removed from the HTML DOM. If you call the
-* preventDefault() method in the event listener for the sessionDisconnect
-* event, the default behavior is prevented, and you can, optionally, clean up Subscriber objects
-* using your own code).
-*
-*
To see whether an event has a default behavior, check the cancelable property of
-* the event object.
-*
-*
Call the preventDefault() method in the event listener function for the event.
-*
-* @method #preventDefault
-* @memberof SessionDisconnectEvent
-*/
+ /**
+ * Prevents the default behavior associated with the event from taking place.
+ *
+ *
+ * For the sessionDisconnectEvent, the default behavior is that all
+ * Subscriber objects are unsubscribed and removed from the HTML DOM. Each
+ * Subscriber object dispatches a destroyed event when the element
+ * is removed from the HTML DOM. If you call the preventDefault()
+ * method in the event listener for the sessionDisconnect event,
+ * the default behavior is prevented, and you can, optionally, clean up
+ * Subscriber objects using your own code). *
+ *
+ *
+ * To see whether an event has a default behavior, check the
+ * cancelable property of the event object.
+ *
*
+ *
+ * Call the preventDefault() method in the event listener function
+ * for the event.
+ *
+ *
+ * @method #preventDefault
+ * @memberof SessionDisconnectEvent
+ */
-/**
- * The Session object dispatches a streamPropertyChanged event in the
- * following circumstances:
- *
- *
- *
A stream has started or stopped publishing audio or video (see
- * Publisher.publishAudio() and
- * Publisher.publishVideo()).
- * This change results from a call to the publishAudio() or
- * publishVideo() methods of the Publish object. Note that a
- * subscriber's video can be disabled or enabled for reasons other than the
- * publisher disabling or enabling it. A Subscriber object dispatches
- * videoDisabled and videoEnabled events in all
- * conditions that cause the subscriber's stream to be disabled or enabled.
- *
- *
The videoDimensions property of the Stream object has
- * changed (see Stream.videoDimensions).
- *
- *
The videoType property of the Stream object has changed.
- * This can happen in a stream published by a mobile device. (See
- * Stream.videoType.)
- *
- *
- *
- * @class StreamPropertyChangedEvent
- * @property {String} changedProperty The property of the stream that changed. This value
- * is either "hasAudio", "hasVideo", or "videoDimensions".
- * @property {Object} newValue The new value of the property (after the change).
- * @property {Object} oldValue The old value of the property (before the change).
- * @property {Stream} stream The Stream object for which a property has changed.
- *
- * @see Publisher.publishAudio()
- * @see Publisher.publishVideo()
- * @see Stream.videoDimensions
- * @augments Event
- */
- OT.StreamPropertyChangedEvent = function (type, stream, changedProperty, oldValue, newValue) {
+ /**
+ * The Session object dispatches a streamPropertyChanged event in the
+ * following circumstances:
+ *
+ *
+ *
A stream has started or stopped publishing audio or video (see
+ * Publisher.publishAudio() and
+ * Publisher.publishVideo()).
+ * This change results from a call to the publishAudio() or
+ * publishVideo() methods of the Publish object. Note that a
+ * subscriber's video can be disabled or enabled for reasons other than the
+ * publisher disabling or enabling it. A Subscriber object dispatches
+ * videoDisabled and videoEnabled events in all
+ * conditions that cause the subscriber's stream to be disabled or enabled.
+ *
+ *
The videoDimensions property of the Stream object has
+ * changed (see Stream.videoDimensions).
+ *
+ *
The videoType property of the Stream object has changed.
+ * This can happen in a stream published by a mobile device. (See
+ * Stream.videoType.)
+ *
+ *
+ *
+ * @class StreamPropertyChangedEvent
+ * @property {String} changedProperty The property of the stream that changed. This value
+ * is either "hasAudio", "hasVideo", or "videoDimensions".
+ * @property {Object} newValue The new value of the property (after the change).
+ * @property {Object} oldValue The old value of the property (before the change).
+ * @property {Stream} stream The Stream object for which a property has changed.
+ *
+ * @see Publisher.publishAudio()
+ * @see Publisher.publishVideo()
+ * @see Stream.videoDimensions
+ * @augments Event
+ */
+ OT.StreamPropertyChangedEvent = function(type, stream, changedProperty, oldValue, newValue) {
OT.Event.call(this, type, false);
this.type = type;
this.stream = stream;
@@ -18592,7 +19080,7 @@ OT.IntervalRunner = function(callback, frequency) {
this.newValue = newValue;
};
- OT.VideoDimensionsChangedEvent = function (target, oldValue, newValue) {
+ OT.VideoDimensionsChangedEvent = function(target, oldValue, newValue) {
OT.Event.call(this, 'videoDimensionsChanged', false);
this.type = 'videoDimensionsChanged';
this.target = target;
@@ -18600,20 +19088,20 @@ OT.IntervalRunner = function(callback, frequency) {
this.newValue = newValue;
};
-/**
- * Defines event objects for the archiveStarted and archiveStopped events.
- * The Session object dispatches these events when an archive recording of the session starts and
- * stops.
- *
- * @property {String} id The archive ID.
- * @property {String} name The name of the archive. You can assign an archive a name when you create
- * it, using the OpenTok REST API or one of the
- * OpenTok server SDKs.
- *
- * @class ArchiveEvent
- * @augments Event
- */
- OT.ArchiveEvent = function (type, archive) {
+ /**
+ * Defines event objects for the archiveStarted and archiveStopped
+ * events. The Session object dispatches these events when an archive recording of the session
+ * starts and stops.
+ *
+ * @property {String} id The archive ID.
+ * @property {String} name The name of the archive. You can assign an archive a name when you
+ * create it, using the OpenTok REST API or one
+ * of the OpenTok server SDKs.
+ *
+ * @class ArchiveEvent
+ * @augments Event
+ */
+ OT.ArchiveEvent = function(type, archive) {
OT.Event.call(this, type, false);
this.type = type;
this.id = archive.id;
@@ -18622,7 +19110,7 @@ OT.IntervalRunner = function(callback, frequency) {
this.archive = archive;
};
- OT.ArchiveUpdatedEvent = function (stream, key, oldValue, newValue) {
+ OT.ArchiveUpdatedEvent = function(stream, key, oldValue, newValue) {
OT.Event.call(this, 'updated', false);
this.target = stream;
this.changedProperty = key;
@@ -18630,26 +19118,28 @@ OT.IntervalRunner = function(callback, frequency) {
this.newValue = newValue;
};
-/**
- * The Session object dispatches a signal event when the client receives a signal from the session.
- *
- * @class SignalEvent
- * @property {String} type The type assigned to the signal (if there is one). Use the type to
- * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.)
- * @property {String} data The data string sent with the signal (if there is one).
- * @property {Connection} from The Connection corresponding to the client that sent with the signal.
- *
- * @see Session.signal()
- * @see Session events (signal and signal:type)
- * @augments Event
- */
+ /**
+ * The Session object dispatches a signal event when the client receives a signal from the
+ * session.
+ *
+ * @class SignalEvent
+ * @property {String} type The type assigned to the signal (if there is one). Use the type to
+ * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.)
+ * @property {String} data The data string sent with the signal (if there is one).
+ * @property {Connection} from The Connection corresponding to the client that sent with the
+ * signal.
+ *
+ * @see Session.signal()
+ * @see Session events (signal and signal:type)
+ * @augments Event
+ */
OT.SignalEvent = function(type, data, from) {
OT.Event.call(this, type ? 'signal:' + type : OT.Event.names.SIGNAL, false);
this.data = data;
this.from = from;
};
- OT.StreamUpdatedEvent = function (stream, key, oldValue, newValue) {
+ OT.StreamUpdatedEvent = function(stream, key, oldValue, newValue) {
OT.Event.call(this, 'updated', false);
this.target = stream;
this.changedProperty = key;
@@ -18663,61 +19153,61 @@ OT.IntervalRunner = function(callback, frequency) {
this.reason = reason;
};
-/**
- * Defines the event object for the videoDisabled and videoEnabled events
- * dispatched by the Subscriber.
- *
- * @class VideoEnabledChangedEvent
- *
- * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable
- * (true) or not (false). You can cancel the default behavior by
- * calling the preventDefault() method of the event object in the callback
- * function. (See preventDefault().)
- *
- * @property {String} reason The reason the video was disabled or enabled. This can be set to one of
- * the following values:
- *
- *
- *
- *
"publishVideo" — The publisher started or stopped publishing video,
- * by calling publishVideo(true) or publishVideo(false).
- *
- *
"quality" — The OpenTok Media Router starts or stops sending video
- * to the subscriber based on stream quality changes. This feature of the OpenTok Media
- * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber
- * continues to receive the audio stream, if there is one.)
- *
- * If connectivity improves to support video again, the Subscriber object dispatches
- * a videoEnabled event, and the Subscriber resumes receiving video.
- *
- * By default, the Subscriber displays a video disabled indicator when a
- * videoDisabled event with this reason is dispatched and removes the indicator
- * when the videoDisabled event with this reason is dispatched. You can control
- * the display of this icon by calling the setStyle() method of the Subscriber,
- * setting the videoDisabledDisplayMode property(or you can set the style when
- * calling the Session.subscribe() method, setting the style property
- * of the properties parameter).
- *
- * This feature is only available in sessions that use the OpenTok Media Router (sessions with
- * the media mode
- * set to routed), not in sessions with the media mode set to relayed.
- *
- *
- *
"subscribeToVideo" — The subscriber started or stopped subscribing to
- * video, by calling subscribeToVideo(true) or subscribeToVideo(false).
- *
- *
- *
- *
- * @property {Object} target The object that dispatched the event.
- *
- * @property {String} type The type of event: "videoDisabled" or
- * "videoEnabled".
- *
- * @see Subscriber videoDisabled event
- * @see Subscriber videoEnabled event
- * @augments Event
- */
+ /**
+ * Defines the event object for the videoDisabled and videoEnabled
+ * events dispatched by the Subscriber.
+ *
+ * @class VideoEnabledChangedEvent
+ *
+ * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable
+ * (true) or not (false). You can cancel the default behavior by
+ * calling the preventDefault() method of the event object in the callback
+ * function. (See preventDefault().)
+ *
+ * @property {String} reason The reason the video was disabled or enabled. This can be set to one
+ * of the following values:
+ *
+ *
+ *
+ *
"publishVideo" — The publisher started or stopped publishing video,
+ * by calling publishVideo(true) or publishVideo(false).
+ *
+ *
"quality" — The OpenTok Media Router starts or stops sending video
+ * to the subscriber based on stream quality changes. This feature of the OpenTok Media
+ * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber
+ * continues to receive the audio stream, if there is one.)
+ *
+ * If connectivity improves to support video again, the Subscriber object dispatches
+ * a videoEnabled event, and the Subscriber resumes receiving video.
+ *
+ * By default, the Subscriber displays a video disabled indicator when a
+ * videoDisabled event with this reason is dispatched and removes the indicator
+ * when the videoDisabled event with this reason is dispatched. You can control
+ * the display of this icon by calling the setStyle() method of the Subscriber,
+ * setting the videoDisabledDisplayMode property(or you can set the style when
+ * calling the Session.subscribe() method, setting the style property
+ * of the properties parameter).
+ *
+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with
+ * the media mode
+ * set to routed), not in sessions with the media mode set to relayed.
+ *
+ *
+ *
"subscribeToVideo" — The subscriber started or stopped subscribing to
+ * video, by calling subscribeToVideo(true) or
+ * subscribeToVideo(false).
+ *
+ *
+ *
+ * @property {Object} target The object that dispatched the event.
+ *
+ * @property {String} type The type of event: "videoDisabled" or
+ * "videoEnabled".
+ *
+ * @see Subscriber videoDisabled event
+ * @see Subscriber videoEnabled event
+ * @augments Event
+ */
OT.VideoEnabledChangedEvent = function(type, properties) {
OT.Event.call(this, type, false);
this.reason = properties.reason;
@@ -18727,17 +19217,17 @@ OT.IntervalRunner = function(callback, frequency) {
OT.Event.call(this, type, false);
};
-/**
- * Dispatched periodically by a Subscriber or Publisher object to indicate the audio
- * level. This event is dispatched up to 60 times per second, depending on the browser.
- *
- * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically
- * for use in adjusting a user interface element, such as a volume meter. Use a moving average
- * to smooth the data.
- *
- * @class AudioLevelUpdatedEvent
- * @augments Event
- */
+ /**
+ * Dispatched periodically by a Subscriber or Publisher object to indicate the audio
+ * level. This event is dispatched up to 60 times per second, depending on the browser.
+ *
+ * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically
+ * for use in adjusting a user interface element, such as a volume meter. Use a moving average
+ * to smooth the data.
+ *
+ * @class AudioLevelUpdatedEvent
+ * @augments Event
+ */
OT.AudioLevelUpdatedEvent = function(audioLevel) {
OT.Event.call(this, OT.Event.names.AUDIO_LEVEL_UPDATED, false);
this.audioLevel = audioLevel;
@@ -18980,8 +19470,8 @@ OT.registerScreenSharingExtensionHelper('chrome', {
window: false,
browser: false
},
- register: function (extensionID) {
- if(!extensionID) {
+ register: function(extensionID) {
+ if (!extensionID) {
throw new Error('initChromeScreenSharingExtensionHelper: extensionID is required.');
}
@@ -19004,7 +19494,7 @@ OT.registerScreenSharingExtensionHelper('chrome', {
timeout = null;
fn.apply(null, arguments);
};
- if(timeToWait) {
+ if (timeToWait) {
timeout = setTimeout(function() {
delete callbackRegistry[requestId];
fn(new Error('Timeout waiting for response to request.'));
@@ -19014,19 +19504,19 @@ OT.registerScreenSharingExtensionHelper('chrome', {
};
var isAvailable = function(callback) {
- if(!callback) {
+ if (!callback) {
throw new Error('isAvailable: callback is required.');
}
- if(!isChrome) {
+ if (!isChrome) {
setTimeout(callback.bind(null, false));
}
- if(isInstalled !== void 0) {
+ if (isInstalled !== void 0) {
setTimeout(callback.bind(null, isInstalled));
} else {
var requestId = addCallback(function(error, event) {
- if(isInstalled !== true) {
+ if (isInstalled !== true) {
isInstalled = (event === 'extensionLoaded');
}
callback(isInstalled);
@@ -19037,13 +19527,13 @@ OT.registerScreenSharingExtensionHelper('chrome', {
};
var getConstraints = function(source, constraints, callback) {
- if(!callback) {
+ if (!callback) {
throw new Error('getSourceId: callback is required');
}
isAvailable(function(isInstalled) {
- if(isInstalled) {
+ if (isInstalled) {
var requestId = addCallback(function(error, event, payload) {
- if(event === 'permissionDenied') {
+ if (event === 'permissionDenied') {
callback(new Error('PermissionDeniedError'));
} else {
if (!constraints.video) {
@@ -19070,26 +19560,26 @@ OT.registerScreenSharingExtensionHelper('chrome', {
return;
}
- if(!(event.data != null && typeof event.data === 'object')) {
+ if (!(event.data != null && typeof event.data === 'object')) {
return;
}
- if(event.data.from !== 'extension') {
+ if (event.data.from !== 'extension') {
return;
}
var method = event.data[prefix],
payload = event.data.payload;
- if(payload && payload.requestId) {
+ if (payload && payload.requestId) {
var callback = callbackRegistry[payload.requestId];
delete callbackRegistry[payload.requestId];
- if(callback) {
+ if (callback) {
callback(null, method, payload);
}
}
- if(method === 'extensionLoaded') {
+ if (method === 'extensionLoaded') {
isInstalled = true;
}
});
@@ -19106,11 +19596,6 @@ OT.registerScreenSharingExtensionHelper('chrome', {
// tb_require('../helpers/lib/video_element.js')
// tb_require('./events.js')
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OT */
-
-
// id: String | mandatory | immutable
// type: String {video/audio/data/...} | mandatory | immutable
// active: Boolean | mandatory | mutable
@@ -19118,12 +19603,19 @@ OT.registerScreenSharingExtensionHelper('chrome', {
// frameRate: Float | optional | mutable
// height: Integer | optional | mutable
// width: Integer | optional | mutable
+// maxFrameRate: Float | optional | mutable
+// maxHeight: Integer | optional | mutable
+// maxWidth: Integer | optional | mutable
+//
OT.StreamChannel = function(options) {
this.id = options.id;
this.type = options.type;
this.active = OT.$.castToBoolean(options.active);
this.orientation = options.orientation || OT.VideoOrientation.ROTATED_NORMAL;
- if (options.frameRate) this.frameRate = parseFloat(options.frameRate, 10);
+ if (options.frameRate) this.frameRate = parseFloat(options.frameRate);
+ if (options.maxFrameRate) this.maxFrameRate = parseFloat(options.maxFrameRate);
+ if (options.maxWidth) this.maxWidth = parseInt(options.maxWidth, 10);
+ if (options.maxHeight) this.maxHeight = parseInt(options.maxHeight, 10);
this.width = parseInt(options.width, 10);
this.height = parseInt(options.height, 10);
@@ -19139,14 +19631,14 @@ OT.StreamChannel = function(options) {
oldVideoDimensions = {};
for (var key in attributes) {
- if(!attributes.hasOwnProperty(key)) {
+ if (!attributes.hasOwnProperty(key)) {
continue;
}
// we shouldn't really read this before we know the key is valid
var oldValue = this[key];
- switch(key) {
+ switch (key) {
case 'active':
this.active = OT.$.castToBoolean(attributes[key]);
break;
@@ -19206,80 +19698,75 @@ OT.StreamChannel = function(options) {
// tb_require('./events.js')
// tb_require('./stream_channel.js')
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global OT */
-
!(function() {
var validPropertyNames = ['name', 'archiving'];
-/**
- * Specifies a stream. A stream is a representation of a published stream in a session. When a
- * client calls the Session.publish() method, a new stream is
- * created. Properties of the Stream object provide information about the stream.
- *
- *
When a stream is added to a session, the Session object dispatches a
- * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a
- * streamDestroyed event. The StreamEvent object, which defines these event objects,
- * has a stream property, which is an array of Stream object. For details and a code
- * example, see {@link StreamEvent}.
- *
- *
When a connection to a session is made, the Session object dispatches a
- * sessionConnected event, defined by the SessionConnectEvent object. The
- * SessionConnectEvent object has a streams property, which is an array of Stream
- * objects pertaining to the streams in the session at that time. For details and a code example,
- * see {@link SessionConnectEvent}.
- *
- * @class Stream
- * @property {Connection} connection The Connection object corresponding
- * to the connection that is publishing the stream. You can compare this to to the
- * connection property of the Session object to see if the stream is being published
- * by the local web page.
- *
- * @property {Number} creationTime The timestamp for the creation
- * of the stream. This value is calculated in milliseconds. You can convert this value to a
- * Date object by calling new Date(creationTime), where creationTime is
- * the creationTime property of the Stream object.
- *
- * @property {Number} frameRate The frame rate of the video stream. This property is only set if the
- * publisher of the stream specifies a frame rate when calling the OT.initPublisher()
- * method; otherwise, this property is undefined.
- *
- * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the
- * publisher turns on or off audio (by calling
- * Publisher.publishAudio()). When this occurs, the
- * {@link Session} object dispatches a streamPropertyChanged event (see
- * {@link StreamPropertyChangedEvent}).
- *
- * @property {Boolean} hasVideo Whether the stream has video. This property can change if the
- * publisher turns on or off video (by calling
- * Publisher.publishVideo()). When this occurs, the
- * {@link Session} object dispatches a streamPropertyChanged event (see
- * {@link StreamPropertyChangedEvent}).
- *
- * @property {String} name The name of the stream. Publishers can specify a name when publishing
- * a stream (using the publish() method of the publisher's Session object).
- *
- * @property {String} streamId The unique ID of the stream.
- *
- * @property {Object} videoDimensions This object has two properties: width and
- * height. Both are numbers. The width property is the width of the
- * encoded stream; the height property is the height of the encoded stream. (These
- * are independent of the actual width of Publisher and Subscriber objects corresponding to the
- * stream.) This property can change if a stream published from a mobile device resizes, based on
- * a change in the device orientation. When the video dimensions change,
- * the {@link Session} object dispatches a streamPropertyChanged event
- * (see {@link StreamPropertyChangedEvent}).
- *
- * @property {String} videoType The type of video — either "camera" or
- * "screen". A "screen" video uses screen sharing on the publisher
- * as the video source; for other videos, this property is set to "camera".
- * This property can change if a stream published from a mobile device changes from a
- * camera to a screen-sharing video type. When the video type changes, the {@link Session} object
- * dispatches a streamPropertyChanged event (see {@link StreamPropertyChangedEvent}).
- */
-
+ /**
+ * Specifies a stream. A stream is a representation of a published stream in a session. When a
+ * client calls the Session.publish() method, a new stream is
+ * created. Properties of the Stream object provide information about the stream.
+ *
+ *
When a stream is added to a session, the Session object dispatches a
+ * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a
+ * streamDestroyed event. The StreamEvent object, which defines these event objects,
+ * has a stream property, which is an array of Stream object. For details and a code
+ * example, see {@link StreamEvent}.
+ *
+ *
When a connection to a session is made, the Session object dispatches a
+ * sessionConnected event, defined by the SessionConnectEvent object. The
+ * SessionConnectEvent object has a streams property, which is an array of Stream
+ * objects pertaining to the streams in the session at that time. For details and a code example,
+ * see {@link SessionConnectEvent}.
+ *
+ * @class Stream
+ * @property {Connection} connection The Connection object corresponding
+ * to the connection that is publishing the stream. You can compare this to to the
+ * connection property of the Session object to see if the stream is being published
+ * by the local web page.
+ *
+ * @property {Number} creationTime The timestamp for the creation
+ * of the stream. This value is calculated in milliseconds. You can convert this value to a
+ * Date object by calling new Date(creationTime), where creationTime is
+ * the creationTime property of the Stream object.
+ *
+ * @property {Number} frameRate The frame rate of the video stream. This property is only set if
+ * the publisher of the stream specifies a frame rate when calling the
+ * OT.initPublisher() method; otherwise, this property is undefined.
+ *
+ * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the
+ * publisher turns on or off audio (by calling
+ * Publisher.publishAudio()). When this occurs, the
+ * {@link Session} object dispatches a streamPropertyChanged event (see
+ * {@link StreamPropertyChangedEvent}).
+ *
+ * @property {Boolean} hasVideo Whether the stream has video. This property can change if the
+ * publisher turns on or off video (by calling
+ * Publisher.publishVideo()). When this occurs, the
+ * {@link Session} object dispatches a streamPropertyChanged event (see
+ * {@link StreamPropertyChangedEvent}).
+ *
+ * @property {String} name The name of the stream. Publishers can specify a name when publishing
+ * a stream (using the publish() method of the publisher's Session object).
+ *
+ * @property {String} streamId The unique ID of the stream.
+ *
+ * @property {Object} videoDimensions This object has two properties: width and
+ * height. Both are numbers. The width property is the width of the
+ * encoded stream; the height property is the height of the encoded stream. (These
+ * are independent of the actual width of Publisher and Subscriber objects corresponding to the
+ * stream.) This property can change if a stream published from a mobile device resizes, based on
+ * a change in the device orientation. When the video dimensions change,
+ * the {@link Session} object dispatches a streamPropertyChanged event
+ * (see {@link StreamPropertyChangedEvent}).
+ *
+ * @property {String} videoType The type of video — either "camera" or
+ * "screen". A "screen" video uses screen sharing on the publisher
+ * as the video source; for other videos, this property is set to "camera".
+ * This property can change if a stream published from a mobile device changes from a
+ * camera to a screen-sharing video type. When the video type changes, the {@link Session} object
+ * dispatches a streamPropertyChanged event (see {@link StreamPropertyChangedEvent}).
+ */
OT.Stream = function(id, name, creationTime, connection, session, channel) {
var destroyedReason;
@@ -19297,14 +19784,14 @@ OT.StreamChannel = function(options) {
var onChannelUpdate = OT.$.bind(function(channel, key, oldValue, newValue) {
var _key = key;
- switch(_key) {
+ switch (_key) {
case 'active':
_key = channel.type === 'audio' ? 'hasAudio' : 'hasVideo';
this[_key] = newValue;
break;
case 'disableWarning':
- _key = channel.type === 'audio' ? 'audioDisableWarning': 'videoDisableWarning';
+ _key = channel.type === 'audio' ? 'audioDisableWarning' : 'videoDisableWarning';
this[_key] = newValue;
if (!this[channel.type === 'audio' ? 'hasAudio' : 'hasVideo']) {
return; // Do NOT event in this case.
@@ -19321,49 +19808,62 @@ OT.StreamChannel = function(options) {
this[_key] = newValue;
break;
+ case 'videoDimensions':
+ this.videoDimensions = newValue;
+ break;
+
case 'orientation':
case 'width':
case 'height':
- this.videoDimensions = {
- width: channel.width,
- height: channel.height,
- orientation: channel.orientation
- };
-
- // We dispatch this via the videoDimensions key instead
+ // We dispatch this via the videoDimensions key instead so do not
+ // trigger an event for them.
return;
}
- this.dispatchEvent( new OT.StreamUpdatedEvent(this, _key, oldValue, newValue) );
+ this.dispatchEvent(new OT.StreamUpdatedEvent(this, _key, oldValue, newValue));
}, this);
var associatedWidget = OT.$.bind(function() {
- if(this.publisher) {
+ if (this.publisher) {
return this.publisher;
} else {
return OT.subscribers.find(function(subscriber) {
- return subscriber.stream.id === this.id &&
+ return subscriber.stream && subscriber.stream.id === this.id &&
subscriber.session.id === session.id;
- });
+ }, this);
}
}, this);
+ // Returns true if this stream is subscribe to.
+ var isBeingSubscribedTo = OT.$.bind(function() {
+ // @fixme This is not strictly speaking the right test as a stream
+ // can be published and subscribed by the same connection. But the
+ // update features don't handle this case properly right now anyway.
+ //
+ // The issue is that the stream needs to know whether the stream is
+ // 'owned' by a publisher or a subscriber. The reason for that is that
+ // when a Publisher updates a stream channel then we need to send the
+ // `streamChannelUpdate` message, whereas if a Subscriber does then we
+ // need to send `subscriberChannelUpdate`. The current code will always
+ // send `streamChannelUpdate`.
+ return !this.publisher;
+ }, this);
+
// Returns all channels that have a type of +type+.
- this.getChannelsOfType = function (type) {
+ this.getChannelsOfType = function(type) {
return OT.$.filter(this.channel, function(channel) {
return channel.type === type;
});
};
- this.getChannel = function (id) {
- for (var i=0; iwindow.setInterval.
+ *
+ * @param {function()} callback
+ * @param {number} frequency how many times per second we want to execute the callback
+ * @constructor
+ */
+OT.IntervalRunner = function(callback, frequency) {
+ var _callback = callback,
+ _frequency = frequency,
+ _intervalId = null;
- var parseErrorFromJSONDocument,
- onGetResponseCallback,
- onGetErrorCallback;
-
- OT.SessionInfo = function(jsonDocument) {
- var sessionJSON = jsonDocument[0];
-
- OT.log('SessionInfo Response:');
- OT.log(jsonDocument);
-
- /*jshint camelcase:false*/
-
- this.sessionId = sessionJSON.session_id;
- this.partnerId = sessionJSON.partner_id;
- this.sessionStatus = sessionJSON.session_status;
-
- this.messagingServer = sessionJSON.messaging_server_url;
-
- this.messagingURL = sessionJSON.messaging_url;
- this.symphonyAddress = sessionJSON.symphony_address;
-
- this.p2pEnabled = !!(sessionJSON.properties &&
- sessionJSON.properties.p2p &&
- sessionJSON.properties.p2p.preference &&
- sessionJSON.properties.p2p.preference.value === 'enabled');
+ this.start = function() {
+ _intervalId = window.setInterval(_callback, 1000 / _frequency);
};
- // Retrieves Session Info for +session+. The SessionInfo object will be passed
- // to the +onSuccess+ callback. The +onFailure+ callback will be passed an error
- // object and the DOMEvent that relates to the error.
- OT.SessionInfo.get = function(session, onSuccess, onFailure) {
- var sessionInfoURL = OT.properties.apiURL + '/session/' + session.id + '?extended=true',
-
- startTime = OT.$.now(),
-
- options,
-
- validateRawSessionInfo = function(sessionInfo) {
- session.logEvent('Instrumentation', null, 'gsi', OT.$.now() - startTime);
- var error = parseErrorFromJSONDocument(sessionInfo);
- if (error === false) {
- onGetResponseCallback(session, onSuccess, sessionInfo);
- } else {
- onGetErrorCallback(session, onFailure, error, JSON.stringify(sessionInfo));
- }
- };
-
-
- if(OT.$.env.name === 'IE' && OT.$.env.version < 10) {
- sessionInfoURL = sessionInfoURL + '&format=json&token=' + encodeURIComponent(session.token) +
- '&version=1&cache=' + OT.$.uuid();
- options = {
- xdomainrequest: true
- };
- }
- else {
- options = {
- headers: {
- 'X-TB-TOKEN-AUTH': session.token,
- 'X-TB-VERSION': 1
- }
- };
- }
- session.logEvent('SessionInfo', 'Attempt');
- OT.$.getJSON(sessionInfoURL, options, function(error, sessionInfo) {
- if(error) {
- var responseText = sessionInfo;
- onGetErrorCallback(session, onFailure,
- new OT.Error(error.target && error.target.status || error.code, error.message ||
- 'Could not connect to the OpenTok API Server.'), responseText);
- } else {
- validateRawSessionInfo(sessionInfo);
- }
- });
+ this.stop = function() {
+ window.clearInterval(_intervalId);
+ _intervalId = null;
};
-
- var messageServerToClientErrorCodes = {};
- messageServerToClientErrorCodes['404'] = OT.ExceptionCodes.INVALID_SESSION_ID;
- messageServerToClientErrorCodes['409'] = OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE;
- messageServerToClientErrorCodes['400'] = OT.ExceptionCodes.INVALID_SESSION_ID;
- messageServerToClientErrorCodes['403'] = OT.ExceptionCodes.AUTHENTICATION_ERROR;
-
- // Return the error in +jsonDocument+, if there is one. Otherwise it will return
- // false.
- parseErrorFromJSONDocument = function(jsonDocument) {
- if(OT.$.isArray(jsonDocument)) {
-
- var errors = OT.$.filter(jsonDocument, function(node) {
- return node.error != null;
- });
-
- var numErrorNodes = errors.length;
- if(numErrorNodes === 0) {
- return false;
- }
-
- var errorCode = errors[0].error.code;
- var errorMessage;
- if (messageServerToClientErrorCodes[errorCode.toString()]) {
- errorCode = messageServerToClientErrorCodes[errorCode];
- errorMessage = errors[0].error.errorMessage && errors[0].error.errorMessage.message;
- } else {
- errorCode = OT.ErrorCodes.UNEXPECTED_SERVER_RESPONSE;
- errorMessage = 'Unexpected server response. Try this operation again later.';
- }
-
- return {
- code: errorCode,
- message: errorMessage
- };
- } else {
- return {
- code: null,
- message: 'Unknown error: getSessionInfo JSON response was badly formed'
- };
- }
- };
-
- /* jshint camelcase:false */
- onGetResponseCallback = function(session, onSuccess, rawSessionInfo) {
- session.logEvent('SessionInfo', 'Success',
- {messagingServer: rawSessionInfo[0].messaging_server_url});
-
- onSuccess( new OT.SessionInfo(rawSessionInfo) );
- };
- /* jshint camelcase:true */
-
- onGetErrorCallback = function(session, onFailure, error, responseText) {
- var payload = {
- reason:'GetSessionInfo',
- code: (error.code || 'No code'),
- message: error.message + ':' +
- (responseText || 'Empty responseText from API server')
- };
- session.logConnectivityEvent('Failure', payload);
-
- onFailure(error, session);
- };
-
-})(window);
+};
// tb_require('../helpers/helpers.js')
// tb_require('../helpers/lib/properties.js')
@@ -19684,6 +20179,7 @@ OT.StreamChannel = function(options) {
*
@@ -19712,12 +20208,12 @@ OT.StreamChannel = function(options) {
* in an expired token when trying to connect to a session. It can also occur if you pass
* in an invalid token or API key. Make sure that you are generating the token using the
* current version of one of the
- * OpenTok server SDKs.
+ * OpenTok server SDKs.
*
*
@@ -19895,7 +20391,7 @@ OT.StreamChannel = function(options) {
* pass in an expired token when trying to connect to a session. It can also occur if you
* pass in an invalid token or API key. Make sure that you are generating the token using
* the current version of one of the
- * OpenTok server SDKs.
+ * OpenTok server SDKs.
*
Error calling OT.reportIssue(). Check the client's network connection.
+ *
+ *
+ *
*
General errors that can occur when calling any method:
*
*
@@ -19981,7 +20490,7 @@ OT.StreamChannel = function(options) {
};
function _exceptionHandler(component, msg, errorCode, context) {
- var title = errorsCodesToTitle[errorCode],
+ var title = OT.getErrorTitleByCode(errorCode),
contextCopy = context ? OT.$.clone(context) : {};
OT.error('OT.exception :: title: ' + title + ' (' + errorCode + ') msg: ' + msg);
@@ -19994,7 +20503,7 @@ OT.StreamChannel = function(options) {
OT.dispatchEvent(
new OT.ExceptionEvent(OT.Event.names.EXCEPTION, msg, title, errorCode, component, component)
);
- } catch(err) {
+ } catch (err) {
OT.error('OT.exception :: Failed to dispatch exception - ' + err.toString());
// Don't throw an error because this is asynchronous
// don't do an exceptionHandler because that would be recursive
@@ -20015,15 +20524,15 @@ OT.StreamChannel = function(options) {
return errorsCodesToTitle[+code];
};
-// @todo redo this when we have time to tidy up
-//
-// @example
-//
-// OT.handleJsException("Descriptive error message", 2000, {
-// session: session,
-// target: stream|publisher|subscriber|session|etc
-// });
-//
+ // @todo redo this when we have time to tidy up
+ //
+ // @example
+ //
+ // OT.handleJsException("Descriptive error message", 2000, {
+ // session: session,
+ // target: stream|publisher|subscriber|session|etc
+ // });
+ //
OT.handleJsException = function(errorMsg, code, options) {
options = options || {};
@@ -20049,19 +20558,6 @@ OT.StreamChannel = function(options) {
_exceptionHandler(options.target, errorMsg, code, context);
};
- // This is a placeholder until error handling can be rewritten
- OT.dispatchError = function (code, message, completionHandler, session) {
- OT.error(code, message);
-
- if (completionHandler && OT.$.isFunction(completionHandler)) {
- completionHandler.call(null, new OT.Error(code, message));
- }
-
- OT.handleJsException(message, code, {
- session: session
- });
- };
-
})(window);
// tb_require('../helpers/helpers.js')
@@ -20100,7 +20596,6 @@ OT.StreamChannel = function(options) {
}
},
-
onDomReady = function() {
OT.$.onDOMUnload(onDomUnload);
@@ -20138,7 +20633,6 @@ OT.StreamChannel = function(options) {
configLoaded();
};
-
OT.Config.on('dynamicConfigChanged', configLoaded);
OT.Config.on('dynamicConfigLoadFailed', configLoadFailed);
@@ -20224,7 +20718,7 @@ OT.checkSystemRequirements = function() {
return systemRequirementsMet;
};
- if(systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) {
+ if (systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) {
OT.analytics.logEvent({
action: 'checkSystemRequirements',
variation: 'notHasRequirements',
@@ -20236,7 +20730,6 @@ OT.checkSystemRequirements = function() {
return systemRequirementsMet;
};
-
/**
* Displays information about system requirments for OpenTok for WebRTC. This
* information is displayed in an iframe element that fills the browser window.
@@ -20249,11 +20742,11 @@ OT.checkSystemRequirements = function() {
* @method OT.upgradeSystemRequirements
* @memberof OT
*/
-OT.upgradeSystemRequirements = function(){
+OT.upgradeSystemRequirements = function() {
// trigger after the OT environment has loaded
- OT.onLoad( function() {
+ OT.onLoad(function() {
- if(OTPlugin.isSupported()) {
+ if (OTPlugin.isSupported()) {
OT.Dialogs.Plugin.promptToInstall().on({
download: function() {
window.location = OTPlugin.pathToInstaller();
@@ -20268,7 +20761,7 @@ OT.upgradeSystemRequirements = function(){
var id = '_upgradeFlash';
- // Load the iframe over the whole page.
+ // Load the iframe over the whole page.
document.body.appendChild((function() {
var d = document.createElement('iframe');
d.id = id;
@@ -20308,12 +20801,12 @@ OT.upgradeSystemRequirements = function(){
// Since this is from an IFRAME within another domain we are going to listen to hash
// changes. The best cross browser solution is to poll for a change in the hashtag.
if (_intervalId) clearInterval(_intervalId);
- _intervalId = setInterval(function(){
+ _intervalId = setInterval(function() {
var hash = document.location.hash,
re = /^#?\d+&/;
if (hash !== _lastHash && re.test(hash)) {
_lastHash = hash;
- if (hash.replace(re, '') === 'close_window'){
+ if (hash.replace(re, '') === 'close_window') {
document.body.removeChild(document.getElementById(id));
document.location.hash = '';
}
@@ -20321,6 +20814,7 @@ OT.upgradeSystemRequirements = function(){
}, 100);
});
};
+
// tb_require('../helpers/helpers.js')
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
@@ -20346,7 +20840,6 @@ OT.ConnectionCapabilities = function(capabilitiesHash) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
/**
* A class defining properties of the capabilities property of a
* Session object. See Session.capabilities.
@@ -20355,8 +20848,7 @@ OT.ConnectionCapabilities = function(capabilitiesHash) {
* and the Session object has dispatched the sessionConnected event.
*
* For more information on token roles, see the
- * generate_token()
- * method of the OpenTok server-side libraries.
+ * Token Creation Overview.
*
* @class Capabilities
*
@@ -20425,8 +20917,8 @@ OT.Capabilities = function(permissions) {
* @property {String} data A string containing metadata describing the
* connection. When you generate a user token string pass the connection data string to the
* generate_token() method of our
- * server-side libraries. You can also generate a token
- * and define connection data on the
+ * server-side libraries. You can
+ * also generate a token and define connection data on the
* Dashboard page.
*/
OT.Connection = function(id, creationTime, data, capabilitiesHash, permissionsHash) {
@@ -20471,10 +20963,9 @@ OT.Connection.fromHash = function(hash) {
hash.creationTime,
hash.data,
OT.$.extend(hash.capablities || {}, { supportsWebRTC: true }),
- hash.permissions || [] );
+ hash.permissions || []);
};
-
// tb_require('../../../helpers/helpers.js')
// tb_require('./message.js')
// tb_require('../../connection.js')
@@ -20515,7 +21006,7 @@ OT.Connection.fromHash = function(hash) {
};
}
- if ( !(toAddress instanceof OT.Connection || toAddress instanceof OT.Session) ) {
+ if (!(toAddress instanceof OT.Connection || toAddress instanceof OT.Session)) {
return {
code: 400,
reason: 'The To field was invalid'
@@ -20534,15 +21025,13 @@ OT.Connection.fromHash = function(hash) {
reason: 'The signal type was null or undefined. Either set it to a String value or ' +
'omit it'
};
- }
- else if (type.length > MAX_SIGNAL_TYPE_LENGTH) {
+ } else if (type.length > MAX_SIGNAL_TYPE_LENGTH) {
error = {
code: 413,
reason: 'The signal type was too long, the maximum length of it is ' +
MAX_SIGNAL_TYPE_LENGTH + ' characters'
};
- }
- else if ( isInvalidType(type) ) {
+ } else if (isInvalidType(type)) {
error = {
code: 400,
reason: 'The signal type was invalid, it can only contain letters, ' +
@@ -20561,8 +21050,7 @@ OT.Connection.fromHash = function(hash) {
reason: 'The signal data was null or undefined. Either set it to a String value or ' +
'omit it'
};
- }
- else {
+ } else {
try {
if (JSON.stringify(data).length > MAX_SIGNAL_DATA_LENGTH) {
error = {
@@ -20571,8 +21059,7 @@ OT.Connection.fromHash = function(hash) {
MAX_SIGNAL_DATA_LENGTH + ' characters'
};
}
- }
- catch(e) {
+ } catch (e) {
error = {code: 400, reason: 'The data field was not valid JSON'};
}
}
@@ -20580,11 +21067,10 @@ OT.Connection.fromHash = function(hash) {
return error;
};
-
this.toRaptorMessage = function() {
var to = this.to;
- if (to && typeof(to) !== 'string') {
+ if (to && typeof to !== 'string') {
to = to.id;
}
@@ -20595,7 +21081,6 @@ OT.Connection.fromHash = function(hash) {
return options;
};
-
this.error = null;
if (options) {
@@ -20653,15 +21138,13 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
_dispatcher,
_completion;
-
//// Private API
var setState = OT.$.statable(this, _states, 'disconnected'),
onConnectComplete = function onConnectComplete(error) {
if (error) {
setState('error');
- }
- else {
+ } else {
setState('connected');
}
@@ -20682,13 +21165,12 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
_dispatcher.onClose(reason);
}, this),
- onError = function onError () {};
- // @todo what does having an error mean? Are they always fatal? Are we disconnected now?
-
+ onError = function onError() {};
+ // @todo what does having an error mean? Are they always fatal? Are we disconnected now?
//// Public API
- this.connect = function (token, sessionInfo, completion) {
+ this.connect = function(token, sessionInfo, completion) {
if (!this.is('disconnected', 'error')) {
OT.warn('Cannot connect the Raptor Socket as it is currently connected. You should ' +
'disconnect first.');
@@ -20764,8 +21246,8 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
return;
}
- this.publish( OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId),
- function (error) {
+ this.publish(OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId),
+ function(error) {
if (error) {
var errorCode,
errorMessage,
@@ -20800,8 +21282,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
}, this));
};
-
- this.disconnect = function (drainSocketBuffer) {
+ this.disconnect = function(drainSocketBuffer) {
if (this.is('disconnected')) return;
setState('disconnecting');
@@ -20814,7 +21295,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
// dict, but if you provide the completion handler it must
// be the last argument.
//
- this.publish = function (message, headers, completion) {
+ this.publish = function(message, headers, completion) {
if (_rumor.isNot('connected')) {
OT.error('OT.Raptor.Socket: cannot publish until the socket is connected.' + message);
return;
@@ -20830,14 +21311,12 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
if (OT.$.isFunction(headers)) {
_headers = {};
_completion = headers;
- }
- else {
+ } else {
_headers = headers;
}
}
if (!_completion && completion && OT.$.isFunction(completion)) _completion = completion;
-
if (_completion) _dispatcher.registerCallback(transactionId, _completion);
OT.debug('OT.Raptor.Socket Publish (ID:' + transactionId + ') ');
@@ -20855,14 +21334,14 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
// Register a new stream against _sessionId
this.streamCreate = function(name, streamId, audioFallbackEnabled, channels, minBitrate,
maxBitrate, completion) {
- var message = OT.Raptor.Message.streams.create( OT.APIKEY,
- _sessionId,
- streamId,
- name,
- audioFallbackEnabled,
- channels,
- minBitrate,
- maxBitrate);
+ var message = OT.Raptor.Message.streams.create(OT.APIKEY,
+ _sessionId,
+ streamId,
+ name,
+ audioFallbackEnabled,
+ channels,
+ minBitrate,
+ maxBitrate);
this.publish(message, function(error, message) {
completion(error, streamId, message);
@@ -20870,42 +21349,42 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
};
this.streamDestroy = function(streamId) {
- this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId) );
+ this.publish(OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId));
};
this.streamChannelUpdate = function(streamId, channelId, attributes) {
- this.publish( OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId,
- streamId, channelId, attributes) );
+ this.publish(OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId,
+ streamId, channelId, attributes));
};
this.subscriberCreate = function(streamId, subscriberId, channelsToSubscribeTo, completion) {
- this.publish( OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId,
- streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion );
+ this.publish(OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId,
+ streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion);
};
this.subscriberDestroy = function(streamId, subscriberId) {
- this.publish( OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId,
- streamId, subscriberId) );
+ this.publish(OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId,
+ streamId, subscriberId));
};
this.subscriberUpdate = function(streamId, subscriberId, attributes) {
- this.publish( OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId,
- streamId, subscriberId, attributes) );
+ this.publish(OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId,
+ streamId, subscriberId, attributes));
};
this.subscriberChannelUpdate = function(streamId, subscriberId, channelId, attributes) {
- this.publish( OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId,
- streamId, subscriberId, channelId, attributes) );
+ this.publish(OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId,
+ streamId, subscriberId, channelId, attributes));
};
this.forceDisconnect = function(connectionIdToDisconnect, completion) {
- this.publish( OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId,
- connectionIdToDisconnect), completion );
+ this.publish(OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId,
+ connectionIdToDisconnect), completion);
};
this.forceUnpublish = function(streamIdToUnpublish, completion) {
- this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId,
- streamIdToUnpublish), completion );
+ this.publish(OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId,
+ streamIdToUnpublish), completion);
};
this.jsepCandidate = function(streamId, candidate) {
@@ -20922,16 +21401,16 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
};
this.jsepOffer = function(uri, offerSdp) {
- this.publish( OT.Raptor.Message.offer(uri, offerSdp) );
+ this.publish(OT.Raptor.Message.offer(uri, offerSdp));
};
this.jsepAnswer = function(streamId, answerSdp) {
- this.publish( OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp) );
+ this.publish(OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp));
};
this.jsepAnswerP2p = function(streamId, subscriberId, answerSdp) {
- this.publish( OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId,
- subscriberId, answerSdp) );
+ this.publish(OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId,
+ subscriberId, answerSdp));
};
this.signal = function(options, completion, logEventFn) {
@@ -20939,13 +21418,13 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
if (!signal.valid) {
if (completion && OT.$.isFunction(completion)) {
- completion( new SignalError(signal.error.code, signal.error.reason), signal.toHash() );
+ completion(new SignalError(signal.error.code, signal.error.reason), signal.toHash());
}
return;
}
- this.publish( signal.toRaptorMessage(), function(err) {
+ this.publish(signal.toRaptorMessage(), function(err) {
var error,
errorCode,
errorMessage,
@@ -20960,7 +21439,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
}
error = new OT.SignalError(errorCode, errorMessage);
} else {
- var typeStr = signal.data? typeof(signal.data) : null;
+ var typeStr = signal.data ? typeof (signal.data) : null;
logEventFn('signal', 'send', {type: typeStr});
}
@@ -20972,7 +21451,7 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
return _rumor && _rumor.id();
};
- if(dispatcher == null) {
+ if (dispatcher == null) {
dispatcher = new OT.Raptor.Dispatcher();
}
_dispatcher = dispatcher;
@@ -21032,7 +21511,6 @@ OT.GetStatsAudioLevelSampler = function(peerConnection) {
};
};
-
/*
* An AudioContext based audio level sampler. It returns the maximum value in the
* last 1024 samples.
@@ -21163,9 +21641,9 @@ OT.Archive = function(id, name, status) {
OT.$.eventing(this);
// Mass update, called by Raptor.Dispatcher
- this._.update = OT.$.bind(function (attributes) {
+ this._.update = OT.$.bind(function(attributes) {
for (var key in attributes) {
- if(!attributes.hasOwnProperty(key)) {
+ if (!attributes.hasOwnProperty(key)) {
continue;
}
var oldValue = this[key];
@@ -21180,6 +21658,240 @@ OT.Archive = function(id, name, status) {
};
+// tb_require('../helpers/helpers.js')
+// tb_require('../helpers/lib/properties.js')
+// tb_require('./events.js')
+
+!(function() {
+ var Anvil = {};
+
+ // @todo These aren't the same for all resource types.
+ var httpToClientCode = {
+ 400: OT.ExceptionCodes.INVALID_SESSION_ID,
+ 403: OT.ExceptionCodes.AUTHENTICATION_ERROR,
+ 404: OT.ExceptionCodes.INVALID_SESSION_ID,
+ 409: OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE
+ };
+
+ var anvilErrors = {
+ RESPONSE_BADLY_FORMED: {
+ code: null,
+ message: 'Unknown error: JSON response was badly formed'
+ },
+
+ UNEXPECTED_SERVER_RESPONSE: {
+ code: OT.ExceptionCodes.UNEXPECTED_SERVER_RESPONSE,
+ message: 'Unexpected server response. Try this operation again later.'
+ }
+ };
+
+ // Transform an error that we don't understand (+originalError+) to a general
+ // UNEXPECTED_SERVER_RESPONSE one. We also include the original error details
+ // on the `error.details` property.
+ //
+ var normaliseUnexpectedError = function normaliseUnexpectedError(originalError) {
+ var error = OT.$.clone(anvilErrors.UNEXPECTED_SERVER_RESPONSE);
+
+ // We don't know this code...capture whatever the original
+ // error and code were for debugging purposes.
+ error.details = {
+ originalCode: originalError.code.toString(),
+ originalMessage: originalError.message
+ };
+
+ return error;
+ };
+
+ Anvil.getRequestParams = function getApiRequestOptions(resourcePath, token) {
+ var url = OT.properties.apiURL + '/' + resourcePath;
+ var options;
+
+ if (OT.$.env.name === 'IE' && OT.$.env.version < 10) {
+ url = url + '&format=json&token=' + encodeURIComponent(token) +
+ '&version=1&cache=' + OT.$.uuid();
+ options = {
+ xdomainrequest: true
+ };
+ } else {
+ options = {
+ headers: {
+ 'X-TB-TOKEN-AUTH': token,
+ 'X-TB-VERSION': 1
+ }
+ };
+ }
+
+ return {
+ url: url,
+ options: options
+ };
+ };
+
+ Anvil.getErrorsFromHTTP = function(httpError) {
+ if (!httpError) {
+ return false;
+ }
+
+ var error = {
+ code: httpError.target && httpError.target.status
+ };
+
+ return error;
+ };
+
+ Anvil.getErrorsFromResponse = function getErrorsFromResponse(responseJson) {
+ if (!OT.$.isArray(responseJson)) {
+ return anvilErrors.RESPONSE_BADLY_FORMED;
+ }
+
+ var error = OT.$.find(responseJson, function(node) {
+ return node.error !== void 0 && node.error !== null;
+ });
+
+ if (!error) {
+ return false;
+ }
+
+ // Yup :-(
+ error = error.error;
+ error.message = error.errorMessage && error.errorMessage.message;
+
+ return error;
+ };
+
+ var normaliseError = function normaliseError(error, responseText) {
+ if (error.code && !httpToClientCode[error.code]) {
+ error = normaliseUnexpectedError(error);
+ } else {
+ error.code = httpToClientCode[error.code];
+ }
+
+ if (responseText.length === 0) {
+ // This is a weird edge case that usually means that there was connectivity
+ // loss after Anvil sent the response but before the client had fully received it
+ error.code = OT.ExceptionCodes.CONNECT_FAILED;
+ responseText = 'Response body was empty, probably due to connectivity loss';
+ } else if (!error.code) {
+ error = normaliseUnexpectedError(error);
+ }
+
+ if (!error.details) error.details = {};
+ error.details.responseText = responseText;
+
+ return error;
+ };
+
+ Anvil.get = function getFromAnvil(resourcePath, token) {
+ var params = Anvil.getRequestParams(resourcePath, token);
+
+ return new OT.$.RSVP.Promise(function(resolve, reject) {
+ OT.$.getJSON(params.url, params.options, function(httpError, responseJson) {
+ var err = Anvil.getErrorsFromHTTP(httpError) || Anvil.getErrorsFromResponse(responseJson);
+ var responseText;
+
+ if (err) {
+ responseText = responseJson && !OT.$.isEmpty(responseJson) ?
+ JSON.stringify(responseJson) : '';
+ err = normaliseError(err, responseText);
+
+ reject(new OT.$.Error(err.message, 'AnvilError', {
+ code: err.code,
+ details: err.details
+ }));
+
+ return;
+ }
+
+ resolve(responseJson[0]);
+ });
+ });
+ };
+
+ OT.Anvil = Anvil;
+
+})(window);
+
+// tb_require('./anvil.js')
+
+!(function() {
+
+ // This sequence defines the delay before retry. Therefore a 0 indicates
+ // that a retry should happen immediately.
+ //
+ // e.g. 0, 600, 1200 means retry immediately, then in 600 ms, then in 1200ms
+ //
+ var retryDelays = [0, 600, 1200];
+
+ // These codes are possibly transient and it's worth retrying if a Anvil request
+ // fails with one of these codes.
+ var transientErrorCodes = [
+ OT.ExceptionCodes.CONNECT_FAILED,
+ OT.ExceptionCodes.UNEXPECTED_SERVER_RESPONSE
+ ];
+
+ OT.SessionInfo = function(rawSessionInfo) {
+ OT.log('SessionInfo Response:');
+ OT.log(rawSessionInfo);
+
+ /*jshint camelcase:false*/
+ //jscs:disable requireCamelCaseOrUpperCaseIdentifiers
+ this.sessionId = rawSessionInfo.session_id;
+ this.partnerId = rawSessionInfo.partner_id;
+ this.sessionStatus = rawSessionInfo.session_status;
+ this.messagingServer = rawSessionInfo.messaging_server_url;
+ this.messagingURL = rawSessionInfo.messaging_url;
+ this.symphonyAddress = rawSessionInfo.symphony_address;
+
+ if (rawSessionInfo.properties) {
+ // `simulcast` is tri-state:
+ // true: simulcast is on for this session
+ // false: simulcast is off for this session
+ // undefined: the developer can choose
+ //
+ this.simulcast = rawSessionInfo.properties.simulcast;
+
+ this.p2pEnabled = !!(rawSessionInfo.properties.p2p &&
+ rawSessionInfo.properties.p2p.preference &&
+ rawSessionInfo.properties.p2p.preference.value === 'enabled');
+ } else {
+ this.p2pEnabled = false;
+ }
+ };
+
+ // Retrieves Session Info for +session+. The SessionInfo object will be passed
+ // to the +onSuccess+ callback. The +onFailure+ callback will be passed an error
+ // object and the DOMEvent that relates to the error.
+ //
+ OT.SessionInfo.get = function(id, token) {
+ var remainingRetryDelays = retryDelays.slice();
+
+ var attempt = function(err, resolve, reject) {
+ if (remainingRetryDelays.length === 0) {
+ reject(err);
+ return;
+ }
+
+ OT.Anvil.get('session/' + id + '?extended=true', token).then(function(anvilResponse) {
+ resolve(new OT.SessionInfo(anvilResponse));
+ }, function(err) {
+ if (OT.$.arrayIndexOf(transientErrorCodes, err.code) > -1) {
+ // This error is possibly transient, so retry
+ setTimeout(function() {
+ attempt(err, resolve, reject);
+ }, remainingRetryDelays.shift());
+ } else {
+ reject(err);
+ }
+ });
+ };
+
+ return new OT.$.RSVP.Promise(function(resolve, reject) {
+ attempt(void 0, resolve, reject);
+ });
+ };
+
+})(window);
+
// tb_require('../helpers/helpers.js')
// tb_require('../helpers/lib/properties.js')
// tb_require('../helpers/lib/analytics.js')
@@ -21189,6 +21901,7 @@ OT.Archive = function(id, name, status) {
/* global OT */
OT.analytics = new OT.Analytics(OT.properties.loggingURL);
+
// tb_require('../helpers/helpers.js')
// tb_require('../helpers/lib/widget_view.js')
// tb_require('./analytics.js')
@@ -21211,7 +21924,6 @@ OT.analytics = new OT.Analytics(OT.properties.loggingURL);
// tb_require('./peer_connection/get_stats_adapter.js')
// tb_require('./peer_connection/get_stats_helpers.js')
-
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
/* global OT */
@@ -21253,10 +21965,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_audioLevelRunner,
_frameRateRestricted = false,
_connectivityAttemptPinger,
- _subscriber = this;
+ _subscriber = this,
+ _isLocalStream;
_properties = OT.$.defaults({}, options, {
showControls: true,
+ testNetwork: false,
fitMode: _stream.defaultFitMode || 'cover'
});
@@ -21285,7 +21999,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
this.once('subscribeComplete', completionHandler);
}
- if(_audioLevelCapable) {
+ if (_audioLevelCapable) {
this.on({
'audioLevelUpdated:added': function(count) {
if (count === 1 && _audioLevelRunner) {
@@ -21310,7 +22024,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
connectionId: _session && _session.isConnected() ?
_session.connection.connectionId : null,
partnerId: _session && _session.isConnected() ? _session.sessionInfo.partnerId : null,
- subscriberId: _widgetId,
+ subscriberId: _widgetId
}];
if (throttle) args.push(throttle);
OT.analytics.logEvent.apply(OT.analytics, args);
@@ -21333,7 +22047,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
recordQOS = OT.$.bind(function(parsedStats) {
var QoSBlob = {
- streamType : 'WebRTC',
+ streamType: 'WebRTC',
width: _container ? Number(OT.$.width(_container.domElement).replace('px', '')) : null,
height: _container ? Number(OT.$.height(_container.domElement).replace('px', '')) : null,
sessionId: _session ? _session.sessionId : null,
@@ -21348,11 +22062,10 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
remoteConnectionId: _stream.connection.connectionId
};
- OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) );
+ OT.analytics.logQOS(OT.$.extend(QoSBlob, parsedStats));
this.trigger('qos', parsedStats);
}, this),
-
stateChangeFailed = function(changeFailed) {
OT.error('Subscriber State Change Failed: ', changeFailed.message);
OT.debug(changeFailed);
@@ -21375,11 +22088,11 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_container.loading(false);
_chrome.showAfterLoading();
- if(_frameRateRestricted) {
+ if (_frameRateRestricted) {
_stream.setRestrictFrameRate(true);
}
- if(_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') {
+ if (_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') {
_audioLevelMeter[_container.audioOnly() ? 'show' : 'hide']();
}
@@ -21462,6 +22175,8 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_lastSubscribeToVideoReason = 'loading';
this.subscribeToVideo(_properties.subscribeToVideo, 'loading');
+ this.setPreferredResolution(_properties.preferredResolution);
+ this.setPreferredFrameRate(_properties.preferredFrameRate);
var videoContainerOptions = {
error: onPeerConnectionFailure,
@@ -21477,7 +22192,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
// We still need to investigate further.
//
var tracks = webRTCStream.getVideoTracks();
- if(tracks.length > 0) {
+ if (tracks.length > 0) {
OT.$.forEach(tracks, function(track) {
track.setEnabled(_stream.hasVideo && _properties.subscribeToVideo);
});
@@ -21518,17 +22233,16 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_streamContainer = null;
}
-
this.trigger('streamRemoved', this);
},
- streamDestroyed = function () {
+ streamDestroyed = function() {
this.disconnect();
},
streamUpdated = function(event) {
- switch(event.changedProperty) {
+ switch (event.changedProperty) {
case 'videoDimensions':
if (!_streamContainer) {
// Ignore videoEmension updates before streamContainer is created OPENTOK-17253
@@ -21564,7 +22278,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
break;
case 'hasAudio':
- // noop
+ // noop
}
},
@@ -21588,7 +22302,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
updateChromeForStyleChange = function(key, value/*, oldValue*/) {
if (!_chrome) return;
- switch(key) {
+ switch (key) {
case 'nameDisplayMode':
_chrome.name.setDisplayMode(value);
_chrome.backingBar.setNameMode(value);
@@ -21702,7 +22416,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
backgroundImageURI: null,
showArchiveStatus: true,
showMicButton: true
- }, _properties.showControls, function (payload) {
+ }, _properties.showControls, function(payload) {
logAnalyticsEvent('SetStyle', 'Subscriber', payload, 0.1);
});
@@ -21721,12 +22435,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
}
};
- // logs an analytics event for getStats every 100 calls
+ // logs an analytics event for getStats on the first call
var notifyGetStatsCalled = (function() {
var callCount = 0;
return function throttlingNotifyGetStatsCalled() {
- if (callCount % 100 === 0) {
- logAnalyticsEvent('getStats', 'Called');
+ if (callCount === 0) {
+ logAnalyticsEvent('GetStats', 'Called');
}
callCount++;
};
@@ -21735,17 +22449,20 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
this.destroy = function(reason, quiet) {
if (_state.isDestroyed()) return;
- if(reason === 'streamDestroyed') {
- if (_state.isAttemptingToSubscribe()) {
+ if (_state.isAttemptingToSubscribe()) {
+ if (reason === 'streamDestroyed') {
// We weren't subscribing yet so the stream was destroyed before we setup
// the PeerConnection or receiving the initial stream.
this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID'));
+ } else {
+ logConnectivityEvent('Canceled', {streamId: _stream.id});
+ _connectivityAttemptPinger.stop();
}
}
_state.set('Destroyed');
- if(_audioLevelRunner) {
+ if (_audioLevelRunner) {
_audioLevelRunner.stop();
}
@@ -21770,7 +22487,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
this.stream = _stream = null;
this.streamId = null;
- this.session =_session = null;
+ this.session = _session = null;
_properties = null;
if (quiet !== true) {
@@ -21834,7 +22551,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
}
}
this.subscribeToVideo(active, 'auto');
- if(!active) {
+ if (!active) {
_chrome.videoDisabledIndicator.disableVideo(true);
}
var payload = active ? {videoEnabled: true} : {videoDisabled: true};
@@ -21939,7 +22656,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
if (_audioVolume !== value) {
OT.warn('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100');
}
- if(_properties.muted && _audioVolume > 0) {
+ if (_properties.muted && _audioVolume > 0) {
_properties.premuteVolume = value;
muteAudio.call(this, false);
}
@@ -21961,7 +22678,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
* @memberOf Subscriber
*/
this.getAudioVolume = function() {
- if(_properties.muted) {
+ if (_properties.muted) {
return 0;
}
if (_streamContainer) return _streamContainer.getAudioVolume();
@@ -22013,18 +22730,18 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
var muteAudio = function(_mute) {
_chrome.muteButton.muted(_mute);
- if(_mute === _properties.mute) {
+ if (_mute === _properties.mute) {
return;
}
- if(OT.$.env.name === 'Chrome' || OTPlugin.isInstalled()) {
+ if (OT.$.env.name === 'Chrome' || OTPlugin.isInstalled()) {
_properties.subscribeMute = _properties.muted = _mute;
this.subscribeToAudio(_properties.subscribeToAudio);
} else {
- if(_mute) {
+ if (_mute) {
_properties.premuteVolume = this.getAudioVolume();
_properties.muted = true;
this.setAudioVolume(0);
- } else if(_properties.premuteVolume || _properties.audioVolume) {
+ } else if (_properties.premuteVolume || _properties.audioVolume) {
_properties.muted = false;
this.setAudioVolume(_properties.premuteVolume || _properties.audioVolume);
}
@@ -22038,7 +22755,6 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
subscribeToVideo: 'subscribeToVideo'
};
-
/**
* Toggles video on and off. Starts subscribing to video (if it is available and
* currently not being subscribed to) when the value is true;
@@ -22070,7 +22786,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
setAudioOnly(!(value && _stream.hasVideo));
- if ( value && _container && _container.video()) {
+ if (value && _container && _container.video() && _stream.hasVideo) {
_container.loading(value);
_container.video().whenTimeIncrements(function() {
_container.loading(false);
@@ -22103,6 +22819,55 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
return this;
};
+ this.setPreferredResolution = function(preferredResolution) {
+ if (_state.isDestroyed() || !_peerConnection) {
+ OT.warn('Cannot set the max Resolution when not subscribing to a publisher');
+ return;
+ }
+
+ _properties.preferredResolution = preferredResolution;
+
+ if (_session.sessionInfo.p2pEnabled) {
+ OT.warn('Subscriber.setPreferredResolution will not work in a P2P Session');
+ return;
+ }
+
+ var curMaxResolution = _stream.getPreferredResolution();
+
+ var isUnchanged = (curMaxResolution && preferredResolution &&
+ curMaxResolution.width === preferredResolution.width &&
+ curMaxResolution.height === preferredResolution.height) ||
+ (!curMaxResolution && !preferredResolution);
+
+ if (isUnchanged) {
+ return;
+ }
+
+ _stream.setPreferredResolution(preferredResolution);
+ };
+
+ this.setPreferredFrameRate = function(preferredFrameRate) {
+ if (_state.isDestroyed() || !_peerConnection) {
+ OT.warn('Cannot set the max frameRate when not subscribing to a publisher');
+ return;
+ }
+
+ _properties.preferredFrameRate = preferredFrameRate;
+
+ if (_session.sessionInfo.p2pEnabled) {
+ OT.warn('Subscriber.setPreferredFrameRate will not work in a P2P Session');
+ return;
+ }
+
+ /* jshint -W116 */
+ if (_stream.getPreferredFrameRate() == preferredFrameRate) {
+ return;
+ }
+ /* jshint +W116 */
+
+ _stream.setPreferredFrameRate(preferredFrameRate);
+ };
+
this.isSubscribing = function() {
return _state.isSubscribing();
};
@@ -22195,12 +22960,12 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
this._ = {
archivingStatus: function(status) {
- if(_chrome) {
+ if (_chrome) {
_chrome.archive.setArchiving(status);
}
},
- getDataChannel: function (label, options, completion) {
+ getDataChannel: function(label, options, completion) {
// @fixme this will fail if it's called before we have a SubscriberPeerConnection.
// I.e. before we have a publisher connection.
if (!_peerConnection) {
@@ -22254,8 +23019,23 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_startConnectingTime = OT.$.now();
- if (_stream.connection.id !== _session.connection.id) {
- logAnalyticsEvent('createPeerConnection', 'Attempt', '');
+ logAnalyticsEvent('createPeerConnection', 'Attempt', '');
+
+ _isLocalStream = _stream.connection.id === _session.connection.id;
+
+ if (!_properties.testNetwork && _isLocalStream) {
+ // Subscribe to yourself edge-case
+ var publisher = _session.getPublisherForStream(_stream);
+ if (!(publisher && publisher._.webRtcStream())) {
+ this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID'));
+ return this;
+ }
+
+ onRemoteStreamAdded.call(this, publisher._.webRtcStream());
+ } else {
+ if (_properties.testNetwork) {
+ this.setAudioVolume(0);
+ }
_state.set('ConnectingToPeer');
@@ -22271,7 +23051,11 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
}, this);
// initialize the peer connection AFTER we've added the event listeners
- _peerConnection.init();
+ _peerConnection.init(function(err) {
+ if (err) {
+ throw err;
+ }
+ });
if (OT.$.hasCapabilities('audioOutputLevelStat')) {
_audioLevelSampler = new OT.GetStatsAudioLevelSampler(_peerConnection, 'out');
@@ -22279,7 +23063,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
_audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext());
}
- if(_audioLevelSampler) {
+ if (_audioLevelSampler) {
var subscriber = this;
// sample with interval to minimise disturbance on animation loop but dispatch the
// event with RAF since the main purpose is animation of a meter
@@ -22294,210 +23078,199 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
});
}, 60);
}
- } else {
- logAnalyticsEvent('createPeerConnection', 'Attempt', '');
- var publisher = _session.getPublisherForStream(_stream);
- if(!(publisher && publisher._.webRtcStream())) {
- this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID'));
- return this;
- }
-
- // Subscribe to yourself edge-case
- onRemoteStreamAdded.call(this, publisher._.webRtcStream());
}
logConnectivityEvent('Attempt', {streamId: _stream.id});
+ /**
+ * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched
+ * up to 60 times per second, depending on the browser. The audioLevel property
+ * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more
+ * information.
+ *
+ * The following example adjusts the value of a meter element that shows volume of the
+ * subscriber. Note that the audio level is adjusted logarithmically and a moving average
+ * is applied:
+ *
This example shows the algorithm used by the default audio level indicator displayed
+ * in an audio-only Subscriber.
+ *
+ * @name audioLevelUpdated
+ * @event
+ * @memberof Subscriber
+ * @see AudioLevelUpdatedEvent
+ */
- /**
- * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched
- * up to 60 times per second, depending on the browser. The audioLevel property
- * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more
- * information.
- *
- * The following example adjusts the value of a meter element that shows volume of the
- * subscriber. Note that the audio level is adjusted logarithmically and a moving average
- * is applied:
- *
This example shows the algorithm used by the default audio level indicator displayed
- * in an audio-only Subscriber.
- *
- * @name audioLevelUpdated
- * @event
- * @memberof Subscriber
- * @see AudioLevelUpdatedEvent
- */
-
-/**
-* Dispatched when the video for the subscriber is disabled.
-*
-* The reason property defines the reason the video was disabled. This can be set to
-* one of the following values:
-*
-*
-*
-*
-*
"publishVideo" — The publisher stopped publishing video by calling
-* publishVideo(false).
-*
-*
"quality" — The OpenTok Media Router stopped sending video
-* to the subscriber based on stream quality changes. This feature of the OpenTok Media
-* Router has a subscriber drop the video stream when connectivity degrades. (The subscriber
-* continues to receive the audio stream, if there is one.)
-*
-* Before sending this event, when the Subscriber's stream quality deteriorates to a level
-* that is low enough that the video stream is at risk of being disabled, the Subscriber
-* dispatches a videoDisableWarning event.
-*
-* If connectivity improves to support video again, the Subscriber object dispatches
-* a videoEnabled event, and the Subscriber resumes receiving video.
-*
-* By default, the Subscriber displays a video disabled indicator when a
-* videoDisabled event with this reason is dispatched and removes the indicator
-* when the videoDisabled event with this reason is dispatched. You can control
-* the display of this icon by calling the setStyle() method of the Subscriber,
-* setting the videoDisabledDisplayMode property(or you can set the style when
-* calling the Session.subscribe() method, setting the style property
-* of the properties parameter).
-*
-* This feature is only available in sessions that use the OpenTok Media Router (sessions with
-* the media mode
-* set to routed), not in sessions with the media mode set to relayed.
-*
-* You can disable this audio-only fallback feature, by setting the
-* audioFallbackEnabled property to false in the options you pass
-* into the OT.initPublisher() method on the publishing client. (See
-* OT.initPublisher().)
-*
-*
-*
"subscribeToVideo" — The subscriber started or stopped subscribing to
-* video, by calling subscribeToVideo(false).
-*
-*
-*
-*
-* @see VideoEnabledChangedEvent
-* @see event:videoDisableWarning
-* @see event:videoEnabled
-* @name videoDisabled
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the video for the subscriber is disabled.
+ *
+ * The reason property defines the reason the video was disabled. This can be set to
+ * one of the following values:
+ *
+ *
+ *
+ *
+ *
"publishVideo" — The publisher stopped publishing video by calling
+ * publishVideo(false).
+ *
+ *
"quality" — The OpenTok Media Router stopped sending video
+ * to the subscriber based on stream quality changes. This feature of the OpenTok Media
+ * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber
+ * continues to receive the audio stream, if there is one.)
+ *
+ * Before sending this event, when the Subscriber's stream quality deteriorates to a level
+ * that is low enough that the video stream is at risk of being disabled, the Subscriber
+ * dispatches a videoDisableWarning event.
+ *
+ * If connectivity improves to support video again, the Subscriber object dispatches
+ * a videoEnabled event, and the Subscriber resumes receiving video.
+ *
+ * By default, the Subscriber displays a video disabled indicator when a
+ * videoDisabled event with this reason is dispatched and removes the indicator
+ * when the videoDisabled event with this reason is dispatched. You can control
+ * the display of this icon by calling the setStyle() method of the Subscriber,
+ * setting the videoDisabledDisplayMode property(or you can set the style when
+ * calling the Session.subscribe() method, setting the style property
+ * of the properties parameter).
+ *
+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with
+ * the media mode
+ * set to routed), not in sessions with the media mode set to relayed.
+ *
+ * You can disable this audio-only fallback feature, by setting the
+ * audioFallbackEnabled property to false in the options you pass
+ * into the OT.initPublisher() method on the publishing client. (See
+ * OT.initPublisher().)
+ *
+ *
+ *
"subscribeToVideo" — The subscriber started or stopped subscribing to
+ * video, by calling subscribeToVideo(false).
+ *
+ *
+ *
+ *
+ * @see VideoEnabledChangedEvent
+ * @see event:videoDisableWarning
+ * @see event:videoEnabled
+ * @name videoDisabled
+ * @event
+ * @memberof Subscriber
*/
-/**
-* Dispatched when the OpenTok Media Router determines that the stream quality has degraded
-* and the video will be disabled if the quality degrades more. If the quality degrades further,
-* the Subscriber disables the video and dispatches a videoDisabled event.
-*
-* By default, the Subscriber displays a video disabled warning indicator when this event
-* is dispatched (and the video is disabled). You can control the display of this icon by
-* calling the setStyle() method and setting the
-* videoDisabledDisplayMode property (or you can set the style when calling
-* the Session.subscribe() method and setting the style property
-* of the properties parameter).
-*
-* This feature is only available in sessions that use the OpenTok Media Router (sessions with
-* the media mode
-* set to routed), not in sessions with the media mode set to relayed.
-*
-* @see Event
-* @see event:videoDisabled
-* @see event:videoDisableWarningLifted
-* @name videoDisableWarning
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the OpenTok Media Router determines that the stream quality has degraded
+ * and the video will be disabled if the quality degrades more. If the quality degrades further,
+ * the Subscriber disables the video and dispatches a videoDisabled event.
+ *
+ * By default, the Subscriber displays a video disabled warning indicator when this event
+ * is dispatched (and the video is disabled). You can control the display of this icon by
+ * calling the setStyle() method and setting the
+ * videoDisabledDisplayMode property (or you can set the style when calling
+ * the Session.subscribe() method and setting the style property
+ * of the properties parameter).
+ *
+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with
+ * the media mode
+ * set to routed), not in sessions with the media mode set to relayed.
+ *
+ * @see Event
+ * @see event:videoDisabled
+ * @see event:videoDisableWarningLifted
+ * @name videoDisableWarning
+ * @event
+ * @memberof Subscriber
*/
-/**
-* Dispatched when the OpenTok Media Router determines that the stream quality has improved
-* to the point at which the video being disabled is not an immediate risk. This event is
-* dispatched after the Subscriber object dispatches a videoDisableWarning event.
-*
-* This feature is only available in sessions that use the OpenTok Media Router (sessions with
-* the media mode
-* set to routed), not in sessions with the media mode set to relayed.
-*
-* @see Event
-* @see event:videoDisabled
-* @see event:videoDisableWarning
-* @name videoDisableWarningLifted
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the OpenTok Media Router determines that the stream quality has improved
+ * to the point at which the video being disabled is not an immediate risk. This event is
+ * dispatched after the Subscriber object dispatches a videoDisableWarning event.
+ *
+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with
+ * the media mode
+ * set to routed), not in sessions with the media mode set to relayed.
+ *
+ * @see Event
+ * @see event:videoDisabled
+ * @see event:videoDisableWarning
+ * @name videoDisableWarningLifted
+ * @event
+ * @memberof Subscriber
*/
-/**
-* Dispatched when the OpenTok Media Router resumes sending video to the subscriber
-* after video was previously disabled.
-*
-* The reason property defines the reason the video was enabled. This can be set to
-* one of the following values:
-*
-*
-*
-*
-*
"publishVideo" — The publisher started publishing video by calling
-* publishVideo(true).
-*
-*
"quality" — The OpenTok Media Router resumed sending video
-* to the subscriber based on stream quality changes. This feature of the OpenTok Media
-* Router has a subscriber drop the video stream when connectivity degrades and then resume
-* the video stream if the stream quality improves.
-*
-* This feature is only available in sessions that use the OpenTok Media Router (sessions with
-* the media mode
-* set to routed), not in sessions with the media mode set to relayed.
-*
-*
-*
"subscribeToVideo" — The subscriber started or stopped subscribing to
-* video, by calling subscribeToVideo(false).
-*
-*
-*
-*
-*
-* To prevent video from resuming, in the videoEnabled event listener,
-* call subscribeToVideo(false) on the Subscriber object.
-*
-* @see VideoEnabledChangedEvent
-* @see event:videoDisabled
-* @name videoEnabled
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the OpenTok Media Router resumes sending video to the subscriber
+ * after video was previously disabled.
+ *
+ * The reason property defines the reason the video was enabled. This can be set to
+ * one of the following values:
+ *
+ *
+ *
+ *
+ *
"publishVideo" — The publisher started publishing video by calling
+ * publishVideo(true).
+ *
+ *
"quality" — The OpenTok Media Router resumed sending video
+ * to the subscriber based on stream quality changes. This feature of the OpenTok Media
+ * Router has a subscriber drop the video stream when connectivity degrades and then resume
+ * the video stream if the stream quality improves.
+ *
+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with
+ * the media mode
+ * set to routed), not in sessions with the media mode set to relayed.
+ *
+ *
+ *
"subscribeToVideo" — The subscriber started or stopped subscribing to
+ * video, by calling subscribeToVideo(false).
+ *
+ *
+ *
+ *
+ *
+ * To prevent video from resuming, in the videoEnabled event listener,
+ * call subscribeToVideo(false) on the Subscriber object.
+ *
+ * @see VideoEnabledChangedEvent
+ * @see event:videoDisabled
+ * @name videoEnabled
+ * @event
+ * @memberof Subscriber
*/
-/**
-* Dispatched when the Subscriber element is removed from the HTML DOM. When this event is
-* dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber.
-* @see Event
-* @name destroyed
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the Subscriber element is removed from the HTML DOM. When this event is
+ * dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber.
+ * @see Event
+ * @name destroyed
+ * @event
+ * @memberof Subscriber
*/
-/**
-* Dispatched when the video dimensions of the video change. This can occur when the
-* stream.videoType property is set to "screen" (for a screen-sharing
-* video stream), and the user resizes the window being captured. It can also occur if the video
-* is being published by a mobile device and the user rotates the device (causing the camera
-* orientation to change).
-* @name videoDimensionsChanged
-* @event
-* @memberof Subscriber
+ /**
+ * Dispatched when the video dimensions of the video change. This can occur when the
+ * stream.videoType property is set to "screen" (for a screen-sharing
+ * video stream), and the user resizes the window being captured. It can also occur if the video
+ * is being published by a mobile device and the user rotates the device (causing the camera
+ * orientation to change).
+ * @name videoDimensionsChanged
+ * @event
+ * @memberof Subscriber
*/
};
@@ -22521,7 +23294,6 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
/**
* The Session object returned by the OT.initSession() method provides access to
* much of the OpenTok functionality.
@@ -22536,7 +23308,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
* @property {Connection} connection The {@link Connection} object for this session. The
* connection property is only available once the Session object dispatches the sessionConnected
* event. The Session object asynchronously dispatches a sessionConnected event in response
- * to a successful call to the connect() method. See: connect and
+ * to a successful call to the connect() method. See: connect and
* {@link Connection}.
* @property {String} sessionId The session ID for this session. You pass this value into the
* OT.initSession() method when you create the Session object. (Note: a Session
@@ -22544,7 +23316,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
* object and the object dispatches a connected event. See {@link OT.initSession} and
* {@link connect}).
* For more information on sessions and session IDs, see
- * Session creation.
+ * Session creation.
*/
OT.Session = function(apiKey, sessionId) {
OT.$.eventing(this);
@@ -22556,7 +23328,7 @@ OT.Session = function(apiKey, sessionId) {
return;
}
- if(sessionId == null) {
+ if (sessionId == null) {
sessionId = apiKey;
apiKey = null;
}
@@ -22593,8 +23365,6 @@ OT.Session = function(apiKey, sessionId) {
_connectivityAttemptPinger,
dispatchError;
-
-
var setState = OT.$.statable(this, [
'disconnected', 'connecting', 'connected', 'disconnecting'
], 'disconnected');
@@ -22604,13 +23374,12 @@ OT.Session = function(apiKey, sessionId) {
this.streams = new OT.$.Collection();
this.archives = new OT.$.Collection();
+ //--------------------------------------
+ // MESSAGE HANDLERS
+ //--------------------------------------
-//--------------------------------------
-// MESSAGE HANDLERS
-//--------------------------------------
-
-// The duplication of this and sessionConnectionFailed will go away when
-// session and messenger are refactored
+ // The duplication of this and sessionConnectionFailed will go away when
+ // session and messenger are refactored
sessionConnectFailed = function(reason, code) {
setState('disconnected');
@@ -22626,7 +23395,7 @@ OT.Session = function(apiKey, sessionId) {
sessionDisconnectedHandler = function(event) {
var reason = event.reason;
- if(reason === 'networkTimedout') {
+ if (reason === 'networkTimedout') {
reason = 'networkDisconnected';
this.logEvent('Connect', 'TimeOutDisconnect', {reason: event.reason});
} else {
@@ -22677,7 +23446,7 @@ OT.Session = function(apiKey, sessionId) {
};
streamCreatedHandler = function(stream) {
- if(stream.connection.id !== this.connection.id) {
+ if (stream.connection.id !== this.connection.id) {
this.dispatchEvent(new OT.StreamEvent(
OT.Event.names.STREAM_CREATED,
stream,
@@ -22696,8 +23465,7 @@ OT.Session = function(apiKey, sessionId) {
return; // These are not public properties, skip top level event for them.
}
- if (propertyName === 'orientation') {
- propertyName = 'videoDimensions';
+ if (propertyName === 'videoDimensions') {
newValue = {width: newValue.width, height: newValue.height};
}
@@ -22714,7 +23482,7 @@ OT.Session = function(apiKey, sessionId) {
// if the stream is one of ours we delegate handling
// to the publisher itself.
- if(stream.connection.id === this.connection.id) {
+ if (stream.connection.id === this.connection.id) {
OT.$.forEach(OT.publishers.where({ streamId: stream.id }), OT.$.bind(function(publisher) {
publisher._.unpublishFromSession(this, reason);
}, this));
@@ -22728,14 +23496,13 @@ OT.Session = function(apiKey, sessionId) {
// If we are subscribed to any of the streams we should unsubscribe
OT.$.forEach(OT.subscribers.where({streamId: stream.id}), function(subscriber) {
if (subscriber.session.id === this.id) {
- if(subscriber.stream) {
+ if (subscriber.stream) {
subscriber.destroy('streamDestroyed');
}
}
}, this);
- } else {
- // @TODO Add a one time warning that this no longer cleans up the publisher
}
+ // @TODO Add a else with a one time warning that this no longer cleans up the publisher
}, this);
this.dispatchEvent(event, defaultAction);
@@ -22754,7 +23521,7 @@ OT.Session = function(apiKey, sessionId) {
propertyName = event.changedProperty,
newValue = event.newValue;
- if(propertyName === 'status' && newValue === 'stopped') {
+ if (propertyName === 'status' && newValue === 'stopped') {
this.dispatchEvent(new OT.ArchiveEvent('archiveStopped', archive));
} else {
this.dispatchEvent(new OT.ArchiveEvent('archiveUpdated', archive));
@@ -22812,7 +23579,6 @@ OT.Session = function(apiKey, sessionId) {
_socket = new OT.Raptor.Socket(_connectionId, _widgetId, socketUrl, symphonyUrl,
OT.SessionDispatcher(this));
-
_socket.connect(_token, this.sessionInfo, OT.$.bind(function(error, sessionState) {
if (error) {
_socket = void 0;
@@ -22825,7 +23591,7 @@ OT.Session = function(apiKey, sessionId) {
OT.debug('OT.Session: Received session state from Raptor', sessionState);
this.connection = this.connections.get(_socket.id());
- if(this.connection) {
+ if (this.connection) {
this.capabilities = this.connection.permissions;
}
@@ -22868,19 +23634,32 @@ OT.Session = function(apiKey, sessionId) {
getSessionInfo = function() {
if (this.is('connecting')) {
- OT.SessionInfo.get(
- this,
- OT.$.bind(onSessionInfoResponse, this),
- OT.$.bind(function(error) {
- sessionConnectFailed.call(this, error.message +
- (error.code ? ' (' + error.code + ')' : ''), error.code);
- }, this)
+ var session = this;
+ this.logEvent('SessionInfo', 'Attempt');
+
+ OT.SessionInfo.get(sessionId, _token).then(
+ onSessionInfoResponse,
+ function(error) {
+ session.logConnectivityEvent('Failure', {
+ reason:'GetSessionInfo',
+ code: (error.code || 'No code'),
+ message: error.message
+ });
+
+ sessionConnectFailed.call(session,
+ error.message + (error.code ? ' (' + error.code + ')' : ''),
+ error.code);
+ }
);
}
};
- onSessionInfoResponse = function(sessionInfo) {
+ onSessionInfoResponse = OT.$.bind(function(sessionInfo) {
if (this.is('connecting')) {
+ this.logEvent('SessionInfo', 'Success', {
+ messagingServer: sessionInfo.messagingServer
+ });
+
var overrides = OT.properties.sessionInfoOverrides;
this.sessionInfo = sessionInfo;
if (overrides != null && typeof overrides === 'object') {
@@ -22902,15 +23681,24 @@ OT.Session = function(apiKey, sessionId) {
connectMessenger.call(this);
}
}
- };
+ }, this);
// Check whether we have permissions to perform the action.
permittedTo = OT.$.bind(function(action) {
return this.capabilities.permittedTo(action);
}, this);
+ // This is a placeholder until error handling can be rewritten
dispatchError = OT.$.bind(function(code, message, completionHandler) {
- OT.dispatchError(code, message, completionHandler, this);
+ OT.error(code, message);
+
+ if (completionHandler && OT.$.isFunction(completionHandler)) {
+ completionHandler.call(null, new OT.Error(code, message));
+ }
+
+ OT.handleJsException(message, code, {
+ session: this
+ });
}, this);
this.logEvent = function(action, variation, payload, options) {
@@ -22984,7 +23772,7 @@ OT.Session = function(apiKey, sessionId) {
_session.logEvent('TestNetwork', 'Attempt', {});
- if(this.isConnected()) {
+ if (this.isConnected()) {
callback(new OT.$.Error('Session connected, cannot test network', 1015));
return;
}
@@ -23030,7 +23818,7 @@ OT.Session = function(apiKey, sessionId) {
.then(function(stats) {
OT.debug('Received stats from webrtcTest: ', stats);
if (stats.bandwidth < testConfig.media.thresholdBitsPerSecond) {
- return OT.$.RSVP.Promise.reject(new OT.$.Error('The detect bandwidth form the WebRTC ' +
+ return OT.$.RSVP.Promise.reject(new OT.$.Error('The detect bandwidth from the WebRTC ' +
'stage of the test was not sufficient to run the HTTP stage of the test', 1553));
}
@@ -23038,7 +23826,7 @@ OT.Session = function(apiKey, sessionId) {
})
.then(function() {
// run the HTTP test only if the PC test was not extended
- if(!webrtcStats.extended) {
+ if (!webrtcStats.extended) {
return OT.httpTest({httpConfig: testConfig.http});
}
})
@@ -23074,103 +23862,103 @@ OT.Session = function(apiKey, sessionId) {
this.logEvent('Connect', variation, payload, options);
};
-/**
-* Connects to an OpenTok session.
-*
-* Upon a successful connection, the completion handler (the second parameter of the method) is
-* invoked without an error object passed in. (If there is an error connecting, the completion
-* handler is invoked with an error object.) Make sure that you have successfully connected to the
-* session before calling other methods of the Session object.
-*
-*
-* The Session object dispatches a connectionCreated event when any client
-* (including your own) connects to to the session.
-*
-*
-*
-* Example
-*
-*
-* The following code initializes a session and sets up an event listener for when the session
-* connects:
-*
-*
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-* // See https://dashboard.tokbox.com/projects
-* var token = ""; // Replace with a generated token that has been assigned the moderator role.
-* // See https://dashboard.tokbox.com/projects
-*
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("sessionConnected", function(sessionConnectEvent) {
-* //
-* });
-* session.connect(token);
-*
-*
-*
-* In this example, the sessionConnectHandler() function is passed an event
-* object of type {@link SessionConnectEvent}.
-*
-*
-*
-* Events dispatched:
-*
-*
-*
-* exception (ExceptionEvent) Dispatched
-* by the OT class locally in the event of an error.
-*
-*
-* connectionCreated (ConnectionEvent)
-* Dispatched by the Session object on all clients connected to the session.
-*
-*
-* sessionConnected (SessionConnectEvent)
-* Dispatched locally by the Session object when the connection is established.
-*
-*
-* @param {String} token The session token. You generate a session token using our
-* server-side libraries or the
-* Dashboard page. For more information, see
-* Connection token creation.
-*
-* @param {Function} completionHandler (Optional) A function to be called when the call to the
-* connect() method succeeds or fails. This function takes one parameter —
-* error (see the Error object).
-* On success, the completionHandler function is not passed any
-* arguments. On error, the function is passed an error object parameter
-* (see the Error object). The
-* error object has two properties: code (an integer) and
-* message (a string), which identify the cause of the failure. The following
-* code adds a completionHandler when calling the connect() method:
-*
-* session.connect(token, function (error) {
-* if (error) {
-* console.log(error.message);
-* } else {
-* console.log("Connected to session.");
-* }
-* });
-*
-*
-* Note that upon connecting to the session, the Session object dispatches a
-* sessionConnected event in addition to calling the completionHandler.
-* The SessionConnectEvent object, which defines the sessionConnected event,
-* includes connections and streams properties, which
-* list the connections and streams in the session when you connect.
-*
+ * Upon a successful connection, the completion handler (the second parameter of the method) is
+ * invoked without an error object passed in. (If there is an error connecting, the completion
+ * handler is invoked with an error object.) Make sure that you have successfully connected to the
+ * session before calling other methods of the Session object.
+ *
+ *
+ * The Session object dispatches a connectionCreated event when any client
+ * (including your own) connects to to the session.
+ *
+ *
+ *
+ * Example
+ *
+ *
+ * The following code initializes a session and sets up an event listener for when the session
+ * connects:
+ *
+ *
+ * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ * // See https://dashboard.tokbox.com/projects
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("sessionConnected", function(sessionConnectEvent) {
+ * //
+ * });
+ * session.connect(token);
+ *
+ *
+ *
+ * In this example, the sessionConnectHandler() function is passed an event
+ * object of type {@link SessionConnectEvent}.
+ *
+ *
+ *
+ * Events dispatched:
+ *
+ *
+ *
+ * exception (ExceptionEvent) Dispatched
+ * by the OT class locally in the event of an error.
+ *
+ *
+ * connectionCreated (ConnectionEvent)
+ * Dispatched by the Session object on all clients connected to the session.
+ *
+ *
+ * sessionConnected (SessionConnectEvent)
+ * Dispatched locally by the Session object when the connection is established.
+ *
+ *
+ * @param {String} token The session token. You generate a session token using our
+ * server-side libraries or the
+ * Dashboard page. For more information, see
+ * Connection token creation.
+ *
+ * @param {Function} completionHandler (Optional) A function to be called when the call to the
+ * connect() method succeeds or fails. This function takes one parameter —
+ * error (see the Error object).
+ * On success, the completionHandler function is not passed any
+ * arguments. On error, the function is passed an error object parameter
+ * (see the Error object). The
+ * error object has two properties: code (an integer) and
+ * message (a string), which identify the cause of the failure. The following
+ * code adds a completionHandler when calling the connect() method:
+ *
+ * Note that upon connecting to the session, the Session object dispatches a
+ * sessionConnected event in addition to calling the completionHandler.
+ * The SessionConnectEvent object, which defines the sessionConnected event,
+ * includes connections and streams properties, which
+ * list the connections and streams in the session when you connect.
+ *
+ *
+ * @see SessionConnectEvent
+ * @method #connect
+ * @memberOf Session
*/
this.connect = function(token) {
- if(apiKey == null && arguments.length > 1 &&
+ if (arguments.length > 1 &&
(typeof arguments[0] === 'string' || typeof arguments[0] === 'number') &&
typeof arguments[1] === 'string') {
- _apiKey = token.toString();
+ if (apiKey == null) _apiKey = token.toString();
token = arguments[1];
}
@@ -23198,7 +23986,7 @@ OT.Session = function(apiKey, sessionId) {
this.once('sessionConnectFailed', completionHandler);
}
- if(_apiKey == null || OT.$.isFunction(_apiKey)) {
+ if (_apiKey == null || OT.$.isFunction(_apiKey)) {
setTimeout(OT.$.bind(
sessionConnectFailed,
this,
@@ -23213,7 +24001,7 @@ OT.Session = function(apiKey, sessionId) {
if (!_sessionId || OT.$.isObject(_sessionId) || OT.$.isArray(_sessionId)) {
var errorMsg;
- if(!_sessionId) {
+ if (!_sessionId) {
errorMsg = 'SessionID is undefined. You must pass a sessionID to initSession.';
} else {
errorMsg = 'SessionID is not a string. You must use string as the session ID passed into ' +
@@ -23244,63 +24032,63 @@ OT.Session = function(apiKey, sessionId) {
return this;
};
-/**
-* Disconnects from the OpenTok session.
-*
-*
-* Calling the disconnect() method ends your connection with the session. In the
-* course of terminating your connection, it also ceases publishing any stream(s) you were
-* publishing.
-*
-*
-* Session objects on remote clients dispatch streamDestroyed events for any
-* stream you were publishing. The Session object dispatches a sessionDisconnected
-* event locally. The Session objects on remote clients dispatch connectionDestroyed
-* events, letting other connections know you have left the session. The
-* {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the
-* sessionDisconnect and connectionDestroyed events each have a
-* reason property. The reason property lets the developer determine
-* whether the connection is being terminated voluntarily and whether any streams are being
-* destroyed as a byproduct of the underlying connection's voluntary destruction.
-*
-*
-* If the session is not currently connected, calling this method causes a warning to be logged.
-* See OT.setLogLevel().
-*
-*
-*
-* Note: If you intend to reuse a Publisher object created using
-* OT.initPublisher() to publish to different sessions sequentially, call either
-* Session.disconnect() or Session.unpublish(). Do not call both.
-* Then call the preventDefault() method of the streamDestroyed or
-* sessionDisconnected event object to prevent the Publisher object from being
-* removed from the page. Be sure to call preventDefault() only if the
-* connection.connectionId property of the Stream object in the event matches the
-* connection.connectionId property of your Session object (to ensure that you are
-* preventing the default behavior for your published streams, not for other streams that you
-* subscribe to).
-*
-*
-*
-* Events dispatched:
-*
-*
-* sessionDisconnected
-* (SessionDisconnectEvent)
-* Dispatched locally when the connection is disconnected.
-*
-*
-* connectionDestroyed (ConnectionEvent)
-* Dispatched on other clients, along with the streamDestroyed event (as warranted).
-*
-*
-*
-* streamDestroyed (StreamEvent)
-* Dispatched on other clients if streams are lost as a result of the session disconnecting.
-*
-*
-* @method #disconnect
-* @memberOf Session
+ /**
+ * Disconnects from the OpenTok session.
+ *
+ *
+ * Calling the disconnect() method ends your connection with the session. In the
+ * course of terminating your connection, it also ceases publishing any stream(s) you were
+ * publishing.
+ *
+ *
+ * Session objects on remote clients dispatch streamDestroyed events for any
+ * stream you were publishing. The Session object dispatches a sessionDisconnected
+ * event locally. The Session objects on remote clients dispatch connectionDestroyed
+ * events, letting other connections know you have left the session. The
+ * {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the
+ * sessionDisconnect and connectionDestroyed events each have a
+ * reason property. The reason property lets the developer determine
+ * whether the connection is being terminated voluntarily and whether any streams are being
+ * destroyed as a byproduct of the underlying connection's voluntary destruction.
+ *
+ *
+ * If the session is not currently connected, calling this method causes a warning to be logged.
+ * See OT.setLogLevel().
+ *
+ *
+ *
+ * Note: If you intend to reuse a Publisher object created using
+ * OT.initPublisher() to publish to different sessions sequentially, call either
+ * Session.disconnect() or Session.unpublish(). Do not call both.
+ * Then call the preventDefault() method of the streamDestroyed or
+ * sessionDisconnected event object to prevent the Publisher object from being
+ * removed from the page. Be sure to call preventDefault() only if the
+ * connection.connectionId property of the Stream object in the event matches the
+ * connection.connectionId property of your Session object (to ensure that you are
+ * preventing the default behavior for your published streams, not for other streams that you
+ * subscribe to).
+ *
+ *
+ *
+ * Events dispatched:
+ *
+ *
+ * sessionDisconnected
+ * (SessionDisconnectEvent)
+ * Dispatched locally when the connection is disconnected.
+ *
+ *
+ * connectionDestroyed (ConnectionEvent)
+ * Dispatched on other clients, along with the streamDestroyed event (as warranted).
+ *
+ *
+ *
+ * streamDestroyed (StreamEvent)
+ * Dispatched on other clients if streams are lost as a result of the session disconnecting.
+ *
+ *
+ * @method #disconnect
+ * @memberOf Session
*/
var disconnect = OT.$.bind(function disconnect(drainSocketBuffer) {
if (_socket && _socket.isNot('disconnected')) {
@@ -23308,8 +24096,7 @@ OT.Session = function(apiKey, sessionId) {
setState('disconnecting');
_socket.disconnect(drainSocketBuffer);
}
- }
- else {
+ } else {
reset();
}
}, this);
@@ -23325,129 +24112,129 @@ OT.Session = function(apiKey, sessionId) {
disconnect(reason !== 'unloaded');
};
-/**
-* The publish() method starts publishing an audio-video stream to the session.
-* The audio-video stream is captured from a local microphone and webcam. Upon successful
-* publishing, the Session objects on all connected clients dispatch the
-* streamCreated event.
-*
-*
-*
-*
You pass a Publisher object as the one parameter of the method. You can initialize a
-* Publisher object by calling the OT.initPublisher()
-* method. Before calling Session.publish().
-*
-*
-*
This method takes an alternate form: publish([targetElement:String,
-* properties:Object]):Publisher In this form, you do not pass a Publisher
-* object into the function. Instead, you pass in a targetElement (the ID of the
-* DOM element that the Publisher will replace) and a properties object that
-* defines options for the Publisher (see OT.initPublisher().)
-* The method returns a new Publisher object, which starts sending an audio-video stream to the
-* session. The remainder of this documentation describes the form that takes a single Publisher
-* object as a parameter.
-*
-*
-* A local display of the published stream is created on the web page by replacing
-* the specified element in the DOM with a streaming video display. The video stream
-* is automatically mirrored horizontally so that users see themselves and movement
-* in their stream in a natural way. If the width and height of the display do not match
-* the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the
-* display.
-*
-*
-*
-* If calling this method creates a new Publisher object and the OpenTok library does not
-* have access to the camera or microphone, the web page alerts the user to grant access
-* to the camera and microphone.
-*
-*
-*
-* The OT object dispatches an exception event if the user's role does not
-* include permissions required to publish. For example, if the user's role is set to subscriber,
-* then they cannot publish. You define a user's role when you create the user token using the
-* generate_token() method of the
-* OpenTok server-side
-* libraries or the Dashboard page.
-* You pass the token string as a parameter of the connect() method of the Session
-* object. See ExceptionEvent and
-* OT.on().
-*
-*
-* The application throws an error if the session is not connected.
-*
-*
-*
Events dispatched:
-*
-* exception (ExceptionEvent) Dispatched
-* by the OT object. This can occur when user's role does not allow publishing (the
-* code property of event object is set to 1500); it can also occur if the c
-* onnection fails to connect (the code property of event object is set to 1013).
-* WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect.
-* The most common cause for failure is a firewall that the protocol cannot traverse.
-*
-*
-* streamCreated (StreamEvent)
-* The stream has been published. The Session object dispatches this on all clients
-* subscribed to the stream, as well as on the publisher's client.
-*
-*
-*
Example
-*
-*
-* The following example publishes a video once the session connects:
-*
-*
-* var sessionId = ""; // Replace with your own session ID.
-* // See https://dashboard.tokbox.com/projects
-* var token = ""; // Replace with a generated token that has been assigned the moderator role.
-* // See https://dashboard.tokbox.com/projects
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("sessionConnected", function (event) {
-* var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
-* // This assumes that there is a DOM element with the ID 'publisher':
-* publisher = OT.initPublisher('publisher', publisherOptions);
-* session.publish(publisher);
-* });
-* session.connect(token);
-*
-*
-* @param {Publisher} publisher A Publisher object, which you initialize by calling the
-* OT.initPublisher() method.
-*
-* @param {Function} completionHandler (Optional) A function to be called when the call to the
-* publish() method succeeds or fails. This function takes one parameter —
-* error. On success, the completionHandler function is not passed any
-* arguments. On error, the function is passed an error object parameter
-* (see the Error object). The
-* error object has two properties: code (an integer) and
-* message (a string), which identify the cause of the failure. Calling
-* publish() fails if the role assigned to your token is not "publisher" or
-* "moderator"; in this case error.code is set to 1500. Calling
-* publish() also fails the client fails to connect; in this case
-* error.code is set to 1013. The following code adds a
-* completionHandler when calling the publish() method:
-*
-* session.publish(publisher, null, function (error) {
-* if (error) {
-* console.log(error.message);
-* } else {
-* console.log("Publishing a stream.");
-* }
-* });
-*
-*
-* @returns The Publisher object for this stream.
-*
-* @method #publish
-* @memberOf Session
+ /**
+ * The publish() method starts publishing an audio-video stream to the session.
+ * The audio-video stream is captured from a local microphone and webcam. Upon successful
+ * publishing, the Session objects on all connected clients dispatch the
+ * streamCreated event.
+ *
+ *
+ *
+ *
You pass a Publisher object as the one parameter of the method. You can initialize a
+ * Publisher object by calling the OT.initPublisher()
+ * method. Before calling Session.publish().
+ *
+ *
+ *
This method takes an alternate form: publish([targetElement:String,
+ * properties:Object]):Publisher In this form, you do not pass a Publisher
+ * object into the function. Instead, you pass in a targetElement (the ID of the
+ * DOM element that the Publisher will replace) and a properties object that
+ * defines options for the Publisher (see OT.initPublisher().)
+ * The method returns a new Publisher object, which starts sending an audio-video stream to the
+ * session. The remainder of this documentation describes the form that takes a single Publisher
+ * object as a parameter.
+ *
+ *
+ * A local display of the published stream is created on the web page by replacing
+ * the specified element in the DOM with a streaming video display. The video stream
+ * is automatically mirrored horizontally so that users see themselves and movement
+ * in their stream in a natural way. If the width and height of the display do not match
+ * the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the
+ * display.
+ *
+ *
+ *
+ * If calling this method creates a new Publisher object and the OpenTok library does not
+ * have access to the camera or microphone, the web page alerts the user to grant access
+ * to the camera and microphone.
+ *
+ *
+ *
+ * The OT object dispatches an exception event if the user's role does not
+ * include permissions required to publish. For example, if the user's role is set to subscriber,
+ * then they cannot publish. You define a user's role when you create the user token using the
+ * generate_token() method of the
+ * OpenTok server-side
+ * libraries or the Dashboard page.
+ * You pass the token string as a parameter of the connect() method of the Session
+ * object. See ExceptionEvent and
+ * OT.on().
+ *
+ *
+ * The application throws an error if the session is not connected.
+ *
+ *
+ *
Events dispatched:
+ *
+ * exception (ExceptionEvent) Dispatched
+ * by the OT object. This can occur when user's role does not allow publishing (the
+ * code property of event object is set to 1500); it can also occur if the c
+ * onnection fails to connect (the code property of event object is set to 1013).
+ * WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect.
+ * The most common cause for failure is a firewall that the protocol cannot traverse.
+ *
+ *
+ * streamCreated (StreamEvent)
+ * The stream has been published. The Session object dispatches this on all clients
+ * subscribed to the stream, as well as on the publisher's client.
+ *
+ *
+ *
Example
+ *
+ *
+ * The following example publishes a video once the session connects:
+ *
+ *
+ * var sessionId = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ * // See https://dashboard.tokbox.com/projects
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("sessionConnected", function (event) {
+ * var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
+ * // This assumes that there is a DOM element with the ID 'publisher':
+ * publisher = OT.initPublisher('publisher', publisherOptions);
+ * session.publish(publisher);
+ * });
+ * session.connect(token);
+ *
+ *
+ * @param {Publisher} publisher A Publisher object, which you initialize by calling the
+ * OT.initPublisher() method.
+ *
+ * @param {Function} completionHandler (Optional) A function to be called when the call to the
+ * publish() method succeeds or fails. This function takes one parameter —
+ * error. On success, the completionHandler function is not passed any
+ * arguments. On error, the function is passed an error object parameter
+ * (see the Error object). The
+ * error object has two properties: code (an integer) and
+ * message (a string), which identify the cause of the failure. Calling
+ * publish() fails if the role assigned to your token is not "publisher" or
+ * "moderator"; in this case error.code is set to 1500. Calling
+ * publish() also fails the client fails to connect; in this case
+ * error.code is set to 1013. The following code adds a
+ * completionHandler when calling the publish() method:
+ *
+ *
+ * @returns The Publisher object for this stream.
+ *
+ * @method #publish
+ * @memberOf Session
*/
this.publish = function(publisher, properties, completionHandler) {
- if(typeof publisher === 'function') {
+ if (typeof publisher === 'function') {
completionHandler = publisher;
publisher = undefined;
}
- if(typeof properties === 'function') {
+ if (typeof properties === 'function') {
completionHandler = properties;
properties = undefined;
}
@@ -23463,7 +24250,7 @@ OT.Session = function(apiKey, sessionId) {
},
sessionId: _sessionId,
streamId: (publisher && publisher.stream) ? publisher.stream.id : null,
- partnerId: _apiKey,
+ partnerId: _apiKey
});
if (completionHandler && OT.$.isFunction(completionHandler)) {
@@ -23488,21 +24275,21 @@ OT.Session = function(apiKey, sessionId) {
}
// If the user has passed in an ID of a element then we create a new publisher.
- if (!publisher || typeof(publisher)==='string' || OT.$.isElementNode(publisher)) {
+ if (!publisher || typeof (publisher) === 'string' || OT.$.isElementNode(publisher)) {
// Initiate a new Publisher with the new session credentials
publisher = OT.initPublisher(publisher, properties);
- } else if (publisher instanceof OT.Publisher){
+ } else if (publisher instanceof OT.Publisher) {
// If the publisher already has a session attached to it we can
if ('session' in publisher && publisher.session && 'sessionId' in publisher.session) {
// send a warning message that we can't publish again.
- if( publisher.session.sessionId === this.sessionId){
+ if (publisher.session.sessionId === this.sessionId) {
OT.warn('Cannot publish ' + publisher.guid() + ' again to ' +
this.sessionId + '. Please call session.unpublish(publisher) first.');
} else {
OT.warn('Cannot publish ' + publisher.guid() + ' publisher already attached to ' +
- publisher.session.sessionId+ '. Please call session.unpublish(publisher) first.');
+ publisher.session.sessionId + '. Please call session.unpublish(publisher) first.');
}
}
@@ -23514,16 +24301,19 @@ OT.Session = function(apiKey, sessionId) {
return;
}
- publisher.once('publishComplete', function(err) {
+ publisher.once('publishComplete', function() {
+ var args = Array.prototype.slice.call(arguments),
+ err = args[0];
+
if (err) {
- dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH,
- 'Session.publish :: ' + err.message,
- completionHandler);
- return;
+ err.message = 'Session.publish :: ' + err.message;
+ args[0] = err;
+
+ OT.error(err.code, err.message);
}
if (completionHandler && OT.$.isFunction(completionHandler)) {
- completionHandler.apply(null, arguments);
+ completionHandler.apply(null, args);
}
});
@@ -23534,86 +24324,86 @@ OT.Session = function(apiKey, sessionId) {
return publisher;
};
-/**
-* Ceases publishing the specified publisher's audio-video stream
-* to the session. By default, the local representation of the audio-video stream is
-* removed from the web page. Upon successful termination, the Session object on every
-* connected web page dispatches
-* a streamDestroyed event.
-*
-*
-*
-* To prevent the Publisher from being removed from the DOM, add an event listener for the
-* streamDestroyed event dispatched by the Publisher object and call the
-* preventDefault() method of the event object.
-*
-*
-*
-* Note: If you intend to reuse a Publisher object created using
-* OT.initPublisher() to publish to different sessions sequentially, call
-* either Session.disconnect() or Session.unpublish(). Do not call
-* both. Then call the preventDefault() method of the streamDestroyed
-* or sessionDisconnected event object to prevent the Publisher object from being
-* removed from the page. Be sure to call preventDefault() only if the
-* connection.connectionId property of the Stream object in the event matches the
-* connection.connectionId property of your Session object (to ensure that you are
-* preventing the default behavior for your published streams, not for other streams that you
-* subscribe to).
-*
-*
-*
Events dispatched:
-*
-*
-* streamDestroyed (StreamEvent)
-* The stream associated with the Publisher has been destroyed. Dispatched on by the
-* Publisher on on the Publisher's browser. Dispatched by the Session object on
-* all other connections subscribing to the publisher's stream.
-*
-*
-*
Example
-*
-* The following example publishes a stream to a session and adds a Disconnect link to the
-* web page. Clicking this link causes the stream to stop being published.
-*
-*
-* <script>
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-* // See https://dashboard.tokbox.com/projects
-* var token = "Replace with the TokBox token string provided to you."
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("sessionConnected", function sessionConnectHandler(event) {
-* // This assumes that there is a DOM element with the ID 'publisher':
-* publisher = OT.initPublisher('publisher');
-* session.publish(publisher);
-* });
-* session.connect(token);
-* var publisher;
-*
-* function unpublish() {
-* session.unpublish(publisher);
-* }
-* </script>
-*
-* <body>
-*
-* <div id="publisherContainer/>
-* <br/>
-*
-* <a href="javascript:unpublish()">Stop Publishing</a>
-*
-* </body>
-*
-*
-*
-* @see publish()
-*
-* @see streamDestroyed event
-*
-* @param {Publisher} publisher The Publisher object to stop streaming.
-*
-* @method #unpublish
-* @memberOf Session
+ /**
+ * Ceases publishing the specified publisher's audio-video stream
+ * to the session. By default, the local representation of the audio-video stream is
+ * removed from the web page. Upon successful termination, the Session object on every
+ * connected web page dispatches
+ * a streamDestroyed event.
+ *
+ *
+ *
+ * To prevent the Publisher from being removed from the DOM, add an event listener for the
+ * streamDestroyed event dispatched by the Publisher object and call the
+ * preventDefault() method of the event object.
+ *
+ *
+ *
+ * Note: If you intend to reuse a Publisher object created using
+ * OT.initPublisher() to publish to different sessions sequentially, call
+ * either Session.disconnect() or Session.unpublish(). Do not call
+ * both. Then call the preventDefault() method of the streamDestroyed
+ * or sessionDisconnected event object to prevent the Publisher object from being
+ * removed from the page. Be sure to call preventDefault() only if the
+ * connection.connectionId property of the Stream object in the event matches the
+ * connection.connectionId property of your Session object (to ensure that you are
+ * preventing the default behavior for your published streams, not for other streams that you
+ * subscribe to).
+ *
+ *
+ *
Events dispatched:
+ *
+ *
+ * streamDestroyed (StreamEvent)
+ * The stream associated with the Publisher has been destroyed. Dispatched on by the
+ * Publisher on on the Publisher's browser. Dispatched by the Session object on
+ * all other connections subscribing to the publisher's stream.
+ *
+ *
+ *
Example
+ *
+ * The following example publishes a stream to a session and adds a Disconnect link to the
+ * web page. Clicking this link causes the stream to stop being published.
+ *
+ *
+ * <script>
+ * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var token = "Replace with the TokBox token string provided to you."
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("sessionConnected", function sessionConnectHandler(event) {
+ * // This assumes that there is a DOM element with the ID 'publisher':
+ * publisher = OT.initPublisher('publisher');
+ * session.publish(publisher);
+ * });
+ * session.connect(token);
+ * var publisher;
+ *
+ * function unpublish() {
+ * session.unpublish(publisher);
+ * }
+ * </script>
+ *
+ * <body>
+ *
+ * <div id="publisherContainer/>
+ * <br/>
+ *
+ * <a href="javascript:unpublish()">Stop Publishing</a>
+ *
+ * </body>
+ *
+ *
+ *
+ * @see publish()
+ *
+ * @see streamDestroyed event
+ *
+ * @param {Publisher} publisher The Publisher object to stop streaming.
+ *
+ * @method #unpublish
+ * @memberOf Session
*/
this.unpublish = function(publisher) {
if (!publisher) {
@@ -23625,211 +24415,219 @@ OT.Session = function(apiKey, sessionId) {
publisher._.unpublishFromSession(this, 'unpublished');
};
-
-/**
-* Subscribes to a stream that is available to the session. You can get an array of
-* available streams from the streams property of the sessionConnected
-* and streamCreated events (see
-* SessionConnectEvent and
-* StreamEvent).
-*
-*
-* The subscribed stream is displayed on the local web page by replacing the specified element
-* in the DOM with a streaming video display. If the width and height of the display do not
-* match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit
-* the display. If the stream lacks a video component, a blank screen with an audio indicator
-* is displayed in place of the video stream.
-*
-*
-*
-* The application throws an error if the session is not connected or if the
-* targetElement does not exist in the HTML DOM.
-*
-*
-*
Example
-*
-* The following code subscribes to other clients' streams:
-*
-*
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-* // See https://dashboard.tokbox.com/projects
-*
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("streamCreated", function(event) {
-* subscriber = session.subscribe(event.stream, targetElement);
-* });
-* session.connect(token);
-*
-*
-* @param {Stream} stream The Stream object representing the stream to which we are trying to
-* subscribe.
-*
-* @param {Object} targetElement (Optional) The DOM element or the id attribute of
-* the existing DOM element used to determine the location of the Subscriber video in the HTML
-* DOM. See the insertMode property of the properties parameter. If
-* you do not specify a targetElement, the application appends a new DOM element
-* to the HTML body.
-*
-* @param {Object} properties This is an object that contains the following properties:
-*
-*
audioVolume (Number) The desired audio volume, between 0 and
-* 100, when the Subscriber is first opened (default: 50). After you subscribe to the
-* stream, you can adjust the volume by calling the
-* setAudioVolume() method of the
-* Subscriber object. This volume setting affects local playback only; it does not affect
-* the stream's volume on other clients.
-*
-*
-* fitMode (String) Determines how the video is displayed if the its
-* dimensions do not match those of the DOM element. You can set this property to one of
-* the following values:
-*
-*
-*
-* "cover" — The video is cropped if its dimensions do not match
-* those of the DOM element. This is the default setting for videos that have a
-* camera as the source (for Stream objects with the videoType property
-* set to "camera").
-*
-*
-* "contain" — The video is letter-boxed if its dimensions do not
-* match those of the DOM element. This is the default setting for screen-sharing
-* videos (for Stream objects with the videoType property set to
-* "screen").
-*
-*
-*
-*
-*
height (Number) The desired height, in pixels, of the
-* displayed Subscriber video stream (default: 198). Note: Use the
-* height and width properties to set the dimensions
-* of the Subscriber video; do not set the height and width of the DOM element
-* (using CSS).
-*
-*
-* insertMode (String) Specifies how the Subscriber object will
-* be inserted in the HTML DOM. See the targetElement parameter. This
-* string can have the following values:
-*
-*
"replace" The Subscriber object replaces contents of the
-* targetElement. This is the default.
-*
"after" The Subscriber object is a new element inserted
-* after the targetElement in the HTML DOM. (Both the Subscriber and targetElement
-* have the same parent element.)
-*
"before" The Subscriber object is a new element inserted
-* before the targetElement in the HTML DOM. (Both the Subsciber and targetElement
-* have the same parent element.)
-*
"append" The Subscriber object is a new element added as a
-* child of the targetElement. If there are other child elements, the Subscriber is
-* appended as the last child element of the targetElement.
-*
-*
-*
-*
-* showControls (Boolean) Whether to display the built-in user interface
-* controls for the Subscriber (default: true). These controls include the name
-* display, the audio level indicator, the speaker control button, the video disabled indicator,
-* and the video disabled warning icon. You can turn off all user interface controls by setting
-* this property to false. You can control the display of individual user interface
-* controls by leaving this property set to true (the default) and setting individual
-* properties of the style property.
-*
-*
-* style (Object) An object containing properties that define the initial
-* appearance of user interface controls of the Subscriber. The style object
-* includes the following properties:
-*
-*
audioLevelDisplayMode (String) — How to display the audio level
-* indicator. Possible values are: "auto" (the indicator is displayed when the
-* video is disabled), "off" (the indicator is not displayed), and
-* "on" (the indicator is always displayed).
-*
-*
backgroundImageURI (String) — A URI for an image to display as
-* the background image when a video is not displayed. (A video may not be displayed if
-* you call subscribeToVideo(false) on the Subscriber object). You can pass an
-* http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
-* data URI scheme (instead of http or https) and pass in base-64-encrypted
-* PNG data, such as that obtained from the
-* Subscriber.getImgData() method. For example,
-* you could set the property to "data:VBORw0KGgoAA...", where the portion of
-* the string after "data:" is the result of a call to
-* Subscriber.getImgData(). If the URL or the image data is invalid, the
-* property is ignored (the attempt to set the image fails silently).
-*
-* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer),
-* you cannot set the backgroundImageURI style to a string larger than
-* 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this
-* limitation, you cannot set the backgroundImageURI style to a string obtained
-* with the getImgData() method.
-*
-*
-*
buttonDisplayMode (String) — How to display the speaker controls
-* Possible values are: "auto" (controls are displayed when the stream is first
-* displayed and when the user mouses over the display), "off" (controls are not
-* displayed), and "on" (controls are always displayed).
-*
-*
nameDisplayMode (String) Whether to display the stream name.
-* Possible values are: "auto" (the name is displayed when the stream is first
-* displayed and when the user mouses over the display), "off" (the name is not
-* displayed), and "on" (the name is always displayed).
-*
-*
videoDisabledDisplayMode (String) Whether to display the video
-* disabled indicator and video disabled warning icons for a Subscriber. These icons
-* indicate that the video has been disabled (or is in risk of being disabled for
-* the warning icon) due to poor stream quality. This style only applies to the Subscriber
-* object. Possible values are: "auto" (the icons are automatically when the
-* displayed video is disabled or in risk of being disabled due to poor stream quality),
-* "off" (do not display the icons), and "on" (display the
-* icons). The default setting is "auto"
-*
-*
-*
-*
subscribeToAudio (Boolean) Whether to initially subscribe to audio
-* (if available) for the stream (default: true).
-*
-*
subscribeToVideo (Boolean) Whether to initially subscribe to video
-* (if available) for the stream (default: true).
-*
-*
width (Number) The desired width, in pixels, of the
-* displayed Subscriber video stream (default: 264). Note: Use the
-* height and width properties to set the dimensions
-* of the Subscriber video; do not set the height and width of the DOM element
-* (using CSS).
-*
-*
-*
-* @param {Function} completionHandler (Optional) A function to be called when the call to the
-* subscribe() method succeeds or fails. This function takes one parameter —
-* error. On success, the completionHandler function is not passed any
-* arguments. On error, the function is passed an error object, defined by the
-* Error class, has two properties: code (an integer) and
-* message (a string), which identify the cause of the failure. The following
-* code adds a completionHandler when calling the subscribe() method:
-*
-* session.subscribe(stream, "subscriber", null, function (error) {
-* if (error) {
-* console.log(error.message);
-* } else {
-* console.log("Subscribed to stream: " + stream.id);
-* }
-* });
-*
-*
-* @signature subscribe(stream, targetElement, properties, completionHandler)
-* @returns {Subscriber} The Subscriber object for this stream. Stream control functions
-* are exposed through the Subscriber object.
-* @method #subscribe
-* @memberOf Session
+ /**
+ * Subscribes to a stream that is available to the session. You can get an array of
+ * available streams from the streams property of the sessionConnected
+ * and streamCreated events (see
+ * SessionConnectEvent and
+ * StreamEvent).
+ *
+ *
+ * The subscribed stream is displayed on the local web page by replacing the specified element
+ * in the DOM with a streaming video display. If the width and height of the display do not
+ * match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit
+ * the display. If the stream lacks a video component, a blank screen with an audio indicator
+ * is displayed in place of the video stream.
+ *
+ *
+ *
+ * The application throws an error if the session is not connected or if the
+ * targetElement does not exist in the HTML DOM.
+ *
+ *
+ *
Example
+ *
+ * The following code subscribes to other clients' streams:
+ *
+ *
+ * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("streamCreated", function(event) {
+ * subscriber = session.subscribe(event.stream, targetElement);
+ * });
+ * session.connect(token);
+ *
+ *
+ * @param {Stream} stream The Stream object representing the stream to which we are trying to
+ * subscribe.
+ *
+ * @param {Object} targetElement (Optional) The DOM element or the id attribute of
+ * the existing DOM element used to determine the location of the Subscriber video in the HTML
+ * DOM. See the insertMode property of the properties parameter. If
+ * you do not specify a targetElement, the application appends a new DOM element
+ * to the HTML body.
+ *
+ * @param {Object} properties This is an object that contains the following properties:
+ *
+ *
audioVolume (Number) The desired audio volume, between 0 and
+ * 100, when the Subscriber is first opened (default: 50). After you subscribe to the
+ * stream, you can adjust the volume by calling the
+ * setAudioVolume() method of the
+ * Subscriber object. This volume setting affects local playback only; it does not affect
+ * the stream's volume on other clients.
+ *
+ *
+ * fitMode (String) Determines how the video is displayed if the its
+ * dimensions do not match those of the DOM element. You can set this property to one of
+ * the following values:
+ *
+ *
+ *
+ * "cover" — The video is cropped if its dimensions do not match
+ * those of the DOM element. This is the default setting for videos that have a
+ * camera as the source (for Stream objects with the videoType property
+ * set to "camera").
+ *
+ *
+ * "contain" — The video is letter-boxed if its dimensions do not
+ * match those of the DOM element. This is the default setting for screen-sharing
+ * videos (for Stream objects with the videoType property set to
+ * "screen").
+ *
+ *
+ *
+ *
+ *
+ * height (Number) The desired initial height of the displayed
+ * video in the HTML page (default: 198 pixels). You can specify the number of pixels as
+ * either a number (such as 300) or a string ending in "px" (such as "300px"). Or you can
+ * specify a percentage of the size of the parent element, with a string ending in "%"
+ * (such as "100%"). Note: To resize the video, adjust the CSS of the subscriber's
+ * DOM element (the element property of the Subscriber object) or (if the
+ * height is specified as a percentage) its parent DOM element (see
+ *
+ * Resizing or repositioning a video).
+ *
+ *
+ * insertMode (String) Specifies how the Subscriber object will
+ * be inserted in the HTML DOM. See the targetElement parameter. This
+ * string can have the following values:
+ *
+ *
+ *
"replace" The Subscriber object replaces contents of the
+ * targetElement. This is the default.
+ *
"after" The Subscriber object is a new element inserted
+ * after the targetElement in the HTML DOM. (Both the Subscriber and targetElement
+ * have the same parent element.)
+ *
"before" The Subscriber object is a new element inserted
+ * before the targetElement in the HTML DOM. (Both the Subsciber and targetElement
+ * have the same parent element.)
+ *
"append" The Subscriber object is a new element added as a
+ * child of the targetElement. If there are other child elements, the Subscriber is
+ * appended as the last child element of the targetElement.
+ *
+ *
Do not move the publisher element or its parent elements in the DOM
+ * heirarchy. Use CSS to resize or reposition the publisher video's element
+ * (the element property of the Publisher object) or its parent element (see
+ *
+ * Resizing or repositioning a video).
+ *
+ *
+ * showControls (Boolean) Whether to display the built-in user interface
+ * controls for the Subscriber (default: true). These controls include the name
+ * display, the audio level indicator, the speaker control button, the video disabled indicator,
+ * and the video disabled warning icon. You can turn off all user interface controls by setting
+ * this property to false. You can control the display of individual user interface
+ * controls by leaving this property set to true (the default) and setting
+ * individual properties of the style property.
+ *
+ *
+ * style (Object) An object containing properties that define the initial
+ * appearance of user interface controls of the Subscriber. The style object
+ * includes the following properties:
+ *
+ *
audioLevelDisplayMode (String) — How to display the audio level
+ * indicator. Possible values are: "auto" (the indicator is displayed when the
+ * video is disabled), "off" (the indicator is not displayed), and
+ * "on" (the indicator is always displayed).
+ *
+ *
backgroundImageURI (String) — A URI for an image to display as
+ * the background image when a video is not displayed. (A video may not be displayed if
+ * you call subscribeToVideo(false) on the Subscriber object). You can pass an
+ * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
+ * data URI scheme (instead of http or https) and pass in base-64-encrypted
+ * PNG data, such as that obtained from the
+ * Subscriber.getImgData() method. For example,
+ * you could set the property to "data:VBORw0KGgoAA...", where the portion of
+ * the string after "data:" is the result of a call to
+ * Subscriber.getImgData(). If the URL or the image data is invalid, the
+ * property is ignored (the attempt to set the image fails silently).
+ *
+ *
buttonDisplayMode (String) — How to display the speaker controls
+ * Possible values are: "auto" (controls are displayed when the stream is first
+ * displayed and when the user mouses over the display), "off" (controls are not
+ * displayed), and "on" (controls are always displayed).
+ *
+ *
nameDisplayMode (String) Whether to display the stream name.
+ * Possible values are: "auto" (the name is displayed when the stream is first
+ * displayed and when the user mouses over the display), "off" (the name is not
+ * displayed), and "on" (the name is always displayed).
+ *
+ *
videoDisabledDisplayMode (String) Whether to display the video
+ * disabled indicator and video disabled warning icons for a Subscriber. These icons
+ * indicate that the video has been disabled (or is in risk of being disabled for
+ * the warning icon) due to poor stream quality. This style only applies to the Subscriber
+ * object. Possible values are: "auto" (the icons are automatically when the
+ * displayed video is disabled or in risk of being disabled due to poor stream quality),
+ * "off" (do not display the icons), and "on" (display the
+ * icons). The default setting is "auto"
+ *
+ *
+ *
+ *
subscribeToAudio (Boolean) Whether to initially subscribe to audio
+ * (if available) for the stream (default: true).
+ *
+ *
subscribeToVideo (Boolean) Whether to initially subscribe to video
+ * (if available) for the stream (default: true).
+ *
+ *
+ * width (Number) The desired initial width of the displayed
+ * video in the HTML page (default: 264 pixels). You can specify the number of pixels as
+ * either a number (such as 400) or a string ending in "px" (such as "400px"). Or you can
+ * specify a percentage of the size of the parent element, with a string ending in "%"
+ * (such as "100%"). Note: To resize the video, adjust the CSS of the subscriber's
+ * DOM element (the element property of the Subscriber object) or (if the
+ * width is specified as a percentage) its parent DOM element (see
+ *
+ * Resizing or repositioning a video).
+ *
+ *
+ *
+ *
+ * @param {Function} completionHandler (Optional) A function to be called when the call to the
+ * subscribe() method succeeds or fails. This function takes one parameter —
+ * error. On success, the completionHandler function is not passed any
+ * arguments. On error, the function is passed an error object, defined by the
+ * Error class, has two properties: code (an integer) and
+ * message (a string), which identify the cause of the failure. The following
+ * code adds a completionHandler when calling the subscribe() method:
+ *
+ *
+ * @signature subscribe(stream, targetElement, properties, completionHandler)
+ * @returns {Subscriber} The Subscriber object for this stream. Stream control functions
+ * are exposed through the Subscriber object.
+ * @method #subscribe
+ * @memberOf Session
*/
this.subscribe = function(stream, targetElement, properties, completionHandler) {
- if(typeof targetElement === 'function') {
+ if (typeof targetElement === 'function') {
completionHandler = targetElement;
targetElement = undefined;
properties = undefined;
}
- if(typeof properties === 'function') {
+ if (typeof properties === 'function') {
completionHandler = properties;
properties = undefined;
}
@@ -23855,7 +24653,6 @@ OT.Session = function(apiKey, sessionId) {
return;
}
-
var subscriber = new OT.Subscriber(targetElement, OT.$.extend(properties || {}, {
stream: stream,
session: this
@@ -23875,7 +24672,7 @@ OT.Session = function(apiKey, sessionId) {
dispatchError(errorCode, errorMessage, completionHandler);
- } else if (completionHandler && OT.$.isFunction(completionHandler)) {
+ } else if (completionHandler && OT.$.isFunction(completionHandler)) {
completionHandler.apply(null, arguments);
}
@@ -23887,63 +24684,63 @@ OT.Session = function(apiKey, sessionId) {
};
-/**
-* Stops subscribing to a stream in the session. the display of the audio-video stream is
-* removed from the local web page.
-*
-*
Example
-*
-* The following code subscribes to other clients' streams. For each stream, the code also
-* adds an Unsubscribe link.
-*
-*
-* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-* var sessionID = ""; // Replace with your own session ID.
-* // See https://dashboard.tokbox.com/projects
-* var streams = [];
-*
-* var session = OT.initSession(apiKey, sessionID);
-* session.on("streamCreated", function(event) {
-* var stream = event.stream;
-* displayStream(stream);
-* });
-* session.connect(token);
-*
-* function displayStream(stream) {
-* var div = document.createElement('div');
-* div.setAttribute('id', 'stream' + stream.streamId);
-*
-* var subscriber = session.subscribe(stream, div);
-* subscribers.push(subscriber);
-*
-* var aLink = document.createElement('a');
-* aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
-* aLink.innerHTML = "Unsubscribe";
-*
-* var streamsContainer = document.getElementById('streamsContainer');
-* streamsContainer.appendChild(div);
-* streamsContainer.appendChild(aLink);
-*
-* streams = event.streams;
-* }
-*
-* function unsubscribe(subscriberId) {
-* console.log("unsubscribe called");
-* for (var i = 0; i < subscribers.length; i++) {
-* var subscriber = subscribers[i];
-* if (subscriber.id == subscriberId) {
-* session.unsubscribe(subscriber);
-* }
-* }
-* }
-*
-*
-* @param {Subscriber} subscriber The Subscriber object to unsubcribe.
-*
-* @see subscribe()
-*
-* @method #unsubscribe
-* @memberOf Session
+ /**
+ * Stops subscribing to a stream in the session. the display of the audio-video stream is
+ * removed from the local web page.
+ *
+ *
Example
+ *
+ * The following code subscribes to other clients' streams. For each stream, the code also
+ * adds an Unsubscribe link.
+ *
+ *
+ * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ * // See https://dashboard.tokbox.com/projects
+ * var streams = [];
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("streamCreated", function(event) {
+ * var stream = event.stream;
+ * displayStream(stream);
+ * });
+ * session.connect(token);
+ *
+ * function displayStream(stream) {
+ * var div = document.createElement('div');
+ * div.setAttribute('id', 'stream' + stream.streamId);
+ *
+ * var subscriber = session.subscribe(stream, div);
+ * subscribers.push(subscriber);
+ *
+ * var aLink = document.createElement('a');
+ * aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
+ * aLink.innerHTML = "Unsubscribe";
+ *
+ * var streamsContainer = document.getElementById('streamsContainer');
+ * streamsContainer.appendChild(div);
+ * streamsContainer.appendChild(aLink);
+ *
+ * streams = event.streams;
+ * }
+ *
+ * function unsubscribe(subscriberId) {
+ * console.log("unsubscribe called");
+ * for (var i = 0; i < subscribers.length; i++) {
+ * var subscriber = subscribers[i];
+ * if (subscriber.id == subscriberId) {
+ * session.unsubscribe(subscriber);
+ * }
+ * }
+ * }
+ *
+ *
+ * @param {Subscriber} subscriber The Subscriber object to unsubcribe.
+ *
+ * @see subscribe()
+ *
+ * @method #unsubscribe
+ * @memberOf Session
*/
this.unsubscribe = function(subscriber) {
if (!subscriber) {
@@ -23964,38 +24761,38 @@ OT.Session = function(apiKey, sessionId) {
return true;
};
-/**
-* Returns an array of local Subscriber objects for a given stream.
-*
-* @param {Stream} stream The stream for which you want to find subscribers.
-*
-* @returns {Array} An array of {@link Subscriber} objects for the specified stream.
-*
-* @see unsubscribe()
-* @see Subscriber
-* @see StreamEvent
-* @method #getSubscribersForStream
-* @memberOf Session
+ /**
+ * Returns an array of local Subscriber objects for a given stream.
+ *
+ * @param {Stream} stream The stream for which you want to find subscribers.
+ *
+ * @returns {Array} An array of {@link Subscriber} objects for the specified stream.
+ *
+ * @see unsubscribe()
+ * @see Subscriber
+ * @see StreamEvent
+ * @method #getSubscribersForStream
+ * @memberOf Session
*/
this.getSubscribersForStream = function(stream) {
return OT.subscribers.where({streamId: stream.id});
};
-/**
-* Returns the local Publisher object for a given stream.
-*
-* @param {Stream} stream The stream for which you want to find the Publisher.
-*
-* @returns {Publisher} A Publisher object for the specified stream. Returns
-* null if there is no local Publisher object
-* for the specified stream.
-*
-* @see forceUnpublish()
-* @see Subscriber
-* @see StreamEvent
-*
-* @method #getPublisherForStream
-* @memberOf Session
+ /**
+ * Returns the local Publisher object for a given stream.
+ *
+ * @param {Stream} stream The stream for which you want to find the Publisher.
+ *
+ * @returns {Publisher} A Publisher object for the specified stream. Returns
+ * null if there is no local Publisher object
+ * for the specified stream.
+ *
+ * @see forceUnpublish()
+ * @see Subscriber
+ * @see StreamEvent
+ *
+ * @method #getPublisherForStream
+ * @memberOf Session
*/
this.getPublisherForStream = function(stream) {
var streamId,
@@ -24093,116 +24890,115 @@ OT.Session = function(apiKey, sessionId) {
}
};
-
-/**
-* Sends a signal to each client or a specified client in the session. Specify a
-* to property of the signal parameter to limit the signal to
-* be sent to a specific client; otherwise the signal is sent to each client connected to
-* the session.
-*
-* The following example sends a signal of type "foo" with a specified data payload ("hello")
-* to all clients connected to the session:
-*
-* Calling this method without specifying a recipient client (by setting the to
-* property of the signal parameter) results in multiple signals sent (one to each
-* client in the session). For information on charges for signaling, see the
-* OpenTok pricing page.
-*
-* The following example sends a signal of type "foo" with a data payload ("hello") to a
-* specific client connected to the session:
-*
-* Add an event handler for the signal event to listen for all signals sent in
-* the session. Add an event handler for the signal:type event to listen for
-* signals of a specified type only (replace type, in signal:type,
-* with the type of signal to listen for). The Session object dispatches these events. (See
-* events.)
-*
-* @param {Object} signal An object that contains the following properties defining the signal:
-*
-*
data — (String) The data to send. The limit to the length of data
-* string is 8kB. Do not set the data string to null or
-* undefined.
-*
to — (Connection) A Connection
-* object corresponding to the client that the message is to be sent to. If you do not
-* specify this property, the signal is sent to all clients connected to the session.
-*
type — (String) The type of the signal. You can use the type to
-* filter signals when setting an event handler for the signal:type event
-* (where you replace type with the type string). The maximum length of the
-* type string is 128 characters, and it must contain only letters (A-Z and a-z),
-* numbers (0-9), '-', '_', and '~'.
-*
-*
-*
-*
Each property is optional. If you set none of the properties, you will send a signal
-* with no data or type to each client connected to the session.
-*
-* @param {Function} completionHandler A function that is called when sending the signal
-* succeeds or fails. This function takes one parameter — error.
-* On success, the completionHandler function is not passed any
-* arguments. On error, the function is passed an error object, defined by the
-* Error class. The error object has the following
-* properties:
-*
-*
-*
code — (Number) An error code, which can be one of the following:
-*
-*
-*
400
One of the signal properties — data, type, or to —
-* is invalid.
-*
-*
-*
404
The client specified by the to property is not connected to
-* the session.
-*
-*
-*
413
The type string exceeds the maximum length (128 bytes),
-* or the data string exceeds the maximum size (8 kB).
-*
-*
-*
500
You are not connected to the OpenTok session.
-*
-*
-*
-*
message — (String) A description of the error.
-*
-*
-*
Note that the completionHandler success result (error == null)
-* indicates that the options passed into the Session.signal() method are valid
-* and the signal was sent. It does not indicate that the signal was successfully
-* received by any of the intended recipients.
-*
-* @method #signal
-* @memberOf Session
-* @see signal and signal:type events
+ /**
+ * Sends a signal to each client or a specified client in the session. Specify a
+ * to property of the signal parameter to limit the signal to
+ * be sent to a specific client; otherwise the signal is sent to each client connected to
+ * the session.
+ *
+ * The following example sends a signal of type "foo" with a specified data payload ("hello")
+ * to all clients connected to the session:
+ *
+ * Calling this method without specifying a recipient client (by setting the to
+ * property of the signal parameter) results in multiple signals sent (one to each
+ * client in the session). For information on charges for signaling, see the
+ * OpenTok pricing page.
+ *
+ * The following example sends a signal of type "foo" with a data payload ("hello") to a
+ * specific client connected to the session:
+ *
+ * Add an event handler for the signal event to listen for all signals sent in
+ * the session. Add an event handler for the signal:type event to listen for
+ * signals of a specified type only (replace type, in signal:type,
+ * with the type of signal to listen for). The Session object dispatches these events. (See
+ * events.)
+ *
+ * @param {Object} signal An object that contains the following properties defining the signal:
+ *
+ *
data — (String) The data to send. The limit to the length of data
+ * string is 8kB. Do not set the data string to null or
+ * undefined.
+ *
to — (Connection) A Connection
+ * object corresponding to the client that the message is to be sent to. If you do not
+ * specify this property, the signal is sent to all clients connected to the session.
+ *
type — (String) The type of the signal. You can use the type to
+ * filter signals when setting an event handler for the signal:type event
+ * (where you replace type with the type string). The maximum length of the
+ * type string is 128 characters, and it must contain only letters (A-Z and a-z),
+ * numbers (0-9), '-', '_', and '~'.
+ *
+ *
+ *
+ *
Each property is optional. If you set none of the properties, you will send a signal
+ * with no data or type to each client connected to the session.
+ *
+ * @param {Function} completionHandler A function that is called when sending the signal
+ * succeeds or fails. This function takes one parameter — error.
+ * On success, the completionHandler function is not passed any
+ * arguments. On error, the function is passed an error object, defined by the
+ * Error class. The error object has the following
+ * properties:
+ *
+ *
+ *
code — (Number) An error code, which can be one of the following:
+ *
+ *
+ *
400
One of the signal properties — data, type, or to —
+ * is invalid.
+ *
+ *
+ *
404
The client specified by the to property is not connected to
+ * the session.
+ *
+ *
+ *
413
The type string exceeds the maximum length (128 bytes),
+ * or the data string exceeds the maximum size (8 kB).
+ *
+ *
+ *
500
You are not connected to the OpenTok session.
+ *
+ *
+ *
+ *
message — (String) A description of the error.
+ *
+ *
+ *
Note that the completionHandler success result (error == null)
+ * indicates that the options passed into the Session.signal() method are valid
+ * and the signal was sent. It does not indicate that the signal was successfully
+ * received by any of the intended recipients.
+ *
+ * @method #signal
+ * @memberOf Session
+ * @see signal and signal:type events
*/
this.signal = function(options, completion) {
var _options = options,
@@ -24220,95 +25016,94 @@ OT.Session = function(apiKey, sessionId) {
}
_socket.signal(_options, _completion, this.logEvent);
- if (options && options.data && (typeof(options.data) !== 'string')) {
+ if (options && options.data && typeof options.data !== 'string') {
OT.warn('Signaling of anything other than Strings is deprecated. ' +
'Please update the data property to be a string.');
}
};
-/**
-* Forces a remote connection to leave the session.
-*
-*
-* The forceDisconnect() method is normally used as a moderation tool
-* to remove users from an ongoing session.
-*
-*
-* When a connection is terminated using the forceDisconnect(),
-* sessionDisconnected, connectionDestroyed and
-* streamDestroyed events are dispatched in the same way as they
-* would be if the connection had terminated itself using the disconnect()
-* method. However, the reason property of a {@link ConnectionEvent} or
-* {@link StreamEvent} object specifies "forceDisconnected" as the reason
-* for the destruction of the connection and stream(s).
-*
-*
-* While you can use the forceDisconnect() method to terminate your own connection,
-* calling the disconnect() method is simpler.
-*
-*
-* The OT object dispatches an exception event if the user's role
-* does not include permissions required to force other users to disconnect.
-* You define a user's role when you create the user token using the
-* generate_token() method using
-* OpenTok
-* server-side libraries or the
-* Dashboard page.
-* See ExceptionEvent and OT.on().
-*
-*
-* The application throws an error if the session is not connected.
-*
-*
-*
Events dispatched:
-*
-*
-* connectionDestroyed (ConnectionEvent)
-* On clients other than which had the connection terminated.
-*
-*
-* exception (ExceptionEvent)
-* The user's role does not allow forcing other user's to disconnect (event.code =
-* 1530),
-* or the specified stream is not publishing to the session (event.code = 1535).
-*
-*
-* sessionDisconnected
-* (SessionDisconnectEvent)
-* On the client which has the connection terminated.
-*
-*
-* streamDestroyed (StreamEvent)
-* If streams are stopped as a result of the connection ending.
-*
-*
-* @param {Connection} connection The connection to be disconnected from the session.
-* This value can either be a Connection object or a connection
-* ID (which can be obtained from the connectionId property of the Connection object).
-*
-* @param {Function} completionHandler (Optional) A function to be called when the call to the
-* forceDiscononnect() method succeeds or fails. This function takes one parameter
-* — error. On success, the completionHandler function is
-* not passed any arguments. On error, the function is passed an error object
-* parameter. The error object, defined by the Error
-* class, has two properties: code (an integer)
-* and message (a string), which identify the cause of the failure.
-* Calling forceDisconnect() fails if the role assigned to your
-* token is not "moderator"; in this case error.code is set to 1520. The following
-* code adds a completionHandler when calling the forceDisconnect()
-* method:
-*
-* session.forceDisconnect(connection, function (error) {
-* if (error) {
-* console.log(error);
-* } else {
-* console.log("Connection forced to disconnect: " + connection.id);
-* }
-* });
-*
-*
-* @method #forceDisconnect
-* @memberOf Session
+ /**
+ * Forces a remote connection to leave the session.
+ *
+ *
+ * The forceDisconnect() method is normally used as a moderation tool
+ * to remove users from an ongoing session.
+ *
+ *
+ * When a connection is terminated using the forceDisconnect(),
+ * sessionDisconnected, connectionDestroyed and
+ * streamDestroyed events are dispatched in the same way as they
+ * would be if the connection had terminated itself using the disconnect()
+ * method. However, the reason property of a {@link ConnectionEvent} or
+ * {@link StreamEvent} object specifies "forceDisconnected" as the reason
+ * for the destruction of the connection and stream(s).
+ *
+ *
+ * While you can use the forceDisconnect() method to terminate your own connection,
+ * calling the disconnect() method is simpler.
+ *
+ *
+ * The OT object dispatches an exception event if the user's role
+ * does not include permissions required to force other users to disconnect.
+ * You define a user's role when you create the user token using the
+ * generate_token() method using one of the
+ * OpenTok server-side libraries or
+ * or the Dashboard page.
+ * See ExceptionEvent and OT.on().
+ *
+ *
+ * The application throws an error if the session is not connected.
+ *
+ *
+ *
Events dispatched:
+ *
+ *
+ * connectionDestroyed (ConnectionEvent)
+ * On clients other than which had the connection terminated.
+ *
+ *
+ * exception (ExceptionEvent)
+ * The user's role does not allow forcing other user's to disconnect (event.code =
+ * 1530),
+ * or the specified stream is not publishing to the session (event.code = 1535).
+ *
+ *
+ * sessionDisconnected
+ * (SessionDisconnectEvent)
+ * On the client which has the connection terminated.
+ *
+ *
+ * streamDestroyed (StreamEvent)
+ * If streams are stopped as a result of the connection ending.
+ *
+ *
+ * @param {Connection} connection The connection to be disconnected from the session.
+ * This value can either be a Connection object or a connection
+ * ID (which can be obtained from the connectionId property of the Connection object).
+ *
+ * @param {Function} completionHandler (Optional) A function to be called when the call to the
+ * forceDiscononnect() method succeeds or fails. This function takes one parameter
+ * — error. On success, the completionHandler function is
+ * not passed any arguments. On error, the function is passed an error object
+ * parameter. The error object, defined by the Error
+ * class, has two properties: code (an integer)
+ * and message (a string), which identify the cause of the failure.
+ * Calling forceDisconnect() fails if the role assigned to your
+ * token is not "moderator"; in this case error.code is set to 1520. The following
+ * code adds a completionHandler when calling the forceDisconnect()
+ * method:
+ *
+ *
+ * @method #forceDisconnect
+ * @memberOf Session
*/
this.forceDisconnect = function(connectionOrConnectionId, completionHandler) {
@@ -24319,87 +25114,110 @@ OT.Session = function(apiKey, sessionId) {
return;
}
+ var connectionId = (
+ typeof connectionOrConnectionId === 'string' ?
+ connectionOrConnectionId :
+ connectionOrConnectionId.id
+ );
+
+ var invalidParameterErrorMsg = (
+ 'Invalid Parameter. Check that you have passed valid parameter values into the method call.'
+ );
+
+ if (!connectionId) {
+ dispatchError(
+ OT.ExceptionCodes.INVALID_PARAMETER,
+ invalidParameterErrorMsg,
+ completionHandler
+ );
+
+ return;
+ }
+
var notPermittedErrorMsg = 'This token does not allow forceDisconnect. ' +
'The role must be at least `moderator` to enable this functionality';
- if (permittedTo('forceDisconnect')) {
- var connectionId = typeof connectionOrConnectionId === 'string' ?
- connectionOrConnectionId : connectionOrConnectionId.id;
-
- _socket.forceDisconnect(connectionId, function(err) {
- if (err) {
- dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT,
- notPermittedErrorMsg, completionHandler);
-
- } else if (completionHandler && OT.$.isFunction(completionHandler)) {
- completionHandler.apply(null, arguments);
- }
- });
- } else {
+ if (!permittedTo('forceDisconnect')) {
// if this throws an error the handleJsException won't occur
- dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT,
- notPermittedErrorMsg, completionHandler);
+ dispatchError(
+ OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT,
+ notPermittedErrorMsg,
+ completionHandler
+ );
+
+ return;
}
+
+ _socket.forceDisconnect(connectionId, function(err) {
+ if (err) {
+ dispatchError(
+ OT.ExceptionCodes.INVALID_PARAMETER,
+ invalidParameterErrorMsg,
+ completionHandler
+ );
+ } else if (completionHandler && OT.$.isFunction(completionHandler)) {
+ completionHandler.apply(null, arguments);
+ }
+ });
};
-/**
-* Forces the publisher of the specified stream to stop publishing the stream.
-*
-*
-* Calling this method causes the Session object to dispatch a streamDestroyed
-* event on all clients that are subscribed to the stream (including the client that is
-* publishing the stream). The reason property of the StreamEvent object is
-* set to "forceUnpublished".
-*
-*
-* The OT object dispatches an exception event if the user's role
-* does not include permissions required to force other users to unpublish.
-* You define a user's role when you create the user token using the generate_token()
-* method using the
-* OpenTok
-* server-side libraries or the Dashboard
-* page.
-* You pass the token string as a parameter of the connect() method of the Session
-* object. See ExceptionEvent and
-* OT.on().
-*
-*
-*
Events dispatched:
-*
-*
-* exception (ExceptionEvent)
-* The user's role does not allow forcing other users to unpublish.
-*
-*
-* streamDestroyed (StreamEvent)
-* The stream has been unpublished. The Session object dispatches this on all clients
-* subscribed to the stream, as well as on the publisher's client.
-*
-*
-* @param {Stream} stream The stream to be unpublished.
-*
-* @param {Function} completionHandler (Optional) A function to be called when the call to the
-* forceUnpublish() method succeeds or fails. This function takes one parameter
-* — error. On success, the completionHandler function is
-* not passed any arguments. On error, the function is passed an error object
-* parameter. The error object, defined by the Error
-* class, has two properties: code (an integer)
-* and message (a string), which identify the cause of the failure. Calling
-* forceUnpublish() fails if the role assigned to your token is not "moderator";
-* in this case error.code is set to 1530. The following code adds a
-* completionHandler when calling the forceUnpublish() method:
-*
-* session.forceUnpublish(stream, function (error) {
-* if (error) {
-* console.log(error);
-* } else {
-* console.log("Connection forced to disconnect: " + connection.id);
-* }
-* });
-*
-*
-* @method #forceUnpublish
-* @memberOf Session
+ /**
+ * Forces the publisher of the specified stream to stop publishing the stream.
+ *
+ *
+ * Calling this method causes the Session object to dispatch a streamDestroyed
+ * event on all clients that are subscribed to the stream (including the client that is
+ * publishing the stream). The reason property of the StreamEvent object is
+ * set to "forceUnpublished".
+ *
+ *
+ * The OT object dispatches an exception event if the user's role
+ * does not include permissions required to force other users to unpublish.
+ * You define a user's role when you create the user token using the generate_token()
+ * method using the
+ * OpenTok server-side libraries or the
+ * Dashboard page.
+ * You pass the token string as a parameter of the connect() method of the Session
+ * object. See ExceptionEvent and
+ * OT.on().
+ *
+ *
+ *
Events dispatched:
+ *
+ *
+ * exception (ExceptionEvent)
+ * The user's role does not allow forcing other users to unpublish.
+ *
+ *
+ * streamDestroyed (StreamEvent)
+ * The stream has been unpublished. The Session object dispatches this on all clients
+ * subscribed to the stream, as well as on the publisher's client.
+ *
+ *
+ * @param {Stream} stream The stream to be unpublished.
+ *
+ * @param {Function} completionHandler (Optional) A function to be called when the call to the
+ * forceUnpublish() method succeeds or fails. This function takes one parameter
+ * — error. On success, the completionHandler function is
+ * not passed any arguments. On error, the function is passed an error object
+ * parameter. The error object, defined by the Error
+ * class, has two properties: code (an integer)
+ * and message (a string), which identify the cause of the failure. Calling
+ * forceUnpublish() fails if the role assigned to your token is not "moderator";
+ * in this case error.code is set to 1530. The following code adds a
+ * completionHandler when calling the forceUnpublish() method:
+ *
+ *
+ * @method #forceUnpublish
+ * @memberOf Session
*/
this.forceUnpublish = function(streamOrStreamId, completionHandler) {
if (this.isNot('connected')) {
@@ -24431,223 +25249,219 @@ OT.Session = function(apiKey, sessionId) {
}
};
- this.getStateManager = function() {
- OT.warn('Fixme: Have not implemented session.getStateManager');
- };
-
this.isConnected = function() {
return this.is('connected');
};
this.capabilities = new OT.Capabilities([]);
-/**
- * Dispatched when an archive recording of the session starts.
- *
- * @name archiveStarted
- * @event
- * @memberof Session
- * @see ArchiveEvent
- * @see Archiving overview.
- */
+ /**
+ * Dispatched when an archive recording of the session starts.
+ *
+ * @name archiveStarted
+ * @event
+ * @memberof Session
+ * @see ArchiveEvent
+ * @see Archiving overview.
+ */
-/**
- * Dispatched when an archive recording of the session stops.
- *
- * @name archiveStopped
- * @event
- * @memberof Session
- * @see ArchiveEvent
- * @see Archiving overview.
- */
+ /**
+ * Dispatched when an archive recording of the session stops.
+ *
+ * @name archiveStopped
+ * @event
+ * @memberof Session
+ * @see ArchiveEvent
+ * @see Archiving overview.
+ */
-/**
- * Dispatched when a new client (including your own) has connected to the session, and for
- * every client in the session when you first connect. (The Session object also dispatches
- * a sessionConnected event when your local client connects.)
- *
- * @name connectionCreated
- * @event
- * @memberof Session
- * @see ConnectionEvent
- * @see OT.initSession()
- */
+ /**
+ * Dispatched when a new client (including your own) has connected to the session, and for
+ * every client in the session when you first connect. (The Session object also dispatches
+ * a sessionConnected event when your local client connects.)
+ *
+ * @name connectionCreated
+ * @event
+ * @memberof Session
+ * @see ConnectionEvent
+ * @see OT.initSession()
+ */
-/**
- * A client, other than your own, has disconnected from the session.
- * @name connectionDestroyed
- * @event
- * @memberof Session
- * @see ConnectionEvent
- */
+ /**
+ * A client, other than your own, has disconnected from the session.
+ * @name connectionDestroyed
+ * @event
+ * @memberof Session
+ * @see ConnectionEvent
+ */
-/**
- * The page has connected to an OpenTok session. This event is dispatched asynchronously
- * in response to a successful call to the connect() method of a Session
- * object. Before calling the connect() method, initialize the session by
- * calling the OT.initSession() method. For a code example and more details,
- * see Session.connect().
- * @name sessionConnected
- * @event
- * @memberof Session
- * @see SessionConnectEvent
- * @see Session.connect()
- * @see OT.initSession()
- */
+ /**
+ * The page has connected to an OpenTok session. This event is dispatched asynchronously
+ * in response to a successful call to the connect() method of a Session
+ * object. Before calling the connect() method, initialize the session by
+ * calling the OT.initSession() method. For a code example and more details,
+ * see Session.connect().
+ * @name sessionConnected
+ * @event
+ * @memberof Session
+ * @see SessionConnectEvent
+ * @see Session.connect()
+ * @see OT.initSession()
+ */
-/**
- * The client has disconnected from the session. This event may be dispatched asynchronously
- * in response to a successful call to the disconnect() method of the Session object.
- * The event may also be disptached if a session connection is lost inadvertantly, as in the case
- * of a lost network connection.
- *
- * The default behavior is that all Subscriber objects are unsubscribed and removed from the
- * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is
- * removed from the HTML DOM. If you call the preventDefault() method in the event
- * listener for the sessionDisconnect event, the default behavior is prevented, and
- * you can, optionally, clean up Subscriber objects using your own code.
-*
- * @name sessionDisconnected
- * @event
- * @memberof Session
- * @see Session.disconnect()
- * @see Session.forceDisconnect()
- * @see SessionDisconnectEvent
- */
+ /**
+ * The client has disconnected from the session. This event may be dispatched asynchronously
+ * in response to a successful call to the disconnect() method of the Session object.
+ * The event may also be disptached if a session connection is lost inadvertantly, as in the case
+ * of a lost network connection.
+ *
+ * The default behavior is that all Subscriber objects are unsubscribed and removed from the
+ * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is
+ * removed from the HTML DOM. If you call the preventDefault() method in the event
+ * listener for the sessionDisconnect event, the default behavior is prevented, and
+ * you can, optionally, clean up Subscriber objects using your own code.
+ *
+ * @name sessionDisconnected
+ * @event
+ * @memberof Session
+ * @see Session.disconnect()
+ * @see Session.forceDisconnect()
+ * @see SessionDisconnectEvent
+ */
-/**
- * A new stream, published by another client, has been created on this session. For streams
- * published by your own client, the Publisher object dispatches a streamCreated
- * event. For a code example and more details, see {@link StreamEvent}.
- * @name streamCreated
- * @event
- * @memberof Session
- * @see StreamEvent
- * @see Session.publish()
- */
+ /**
+ * A new stream, published by another client, has been created on this session. For streams
+ * published by your own client, the Publisher object dispatches a streamCreated
+ * event. For a code example and more details, see {@link StreamEvent}.
+ * @name streamCreated
+ * @event
+ * @memberof Session
+ * @see StreamEvent
+ * @see Session.publish()
+ */
-/**
- * A stream from another client has stopped publishing to the session.
- *
- * The default behavior is that all Subscriber objects that are subscribed to the stream are
- * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a
- * destroyed event when the element is removed from the HTML DOM. If you call the
- * preventDefault() method in the event listener for the
- * streamDestroyed event, the default behavior is prevented and you can clean up
- * Subscriber objects using your own code. See
- * Session.getSubscribersForStream().
- *
- * For streams published by your own client, the Publisher object dispatches a
- * streamDestroyed event.
- *
- * For a code example and more details, see {@link StreamEvent}.
- * @name streamDestroyed
- * @event
- * @memberof Session
- * @see StreamEvent
- */
+ /**
+ * A stream from another client has stopped publishing to the session.
+ *
+ * The default behavior is that all Subscriber objects that are subscribed to the stream are
+ * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a
+ * destroyed event when the element is removed from the HTML DOM. If you call the
+ * preventDefault() method in the event listener for the
+ * streamDestroyed event, the default behavior is prevented and you can clean up
+ * Subscriber objects using your own code. See
+ * Session.getSubscribersForStream().
+ *
+ * For streams published by your own client, the Publisher object dispatches a
+ * streamDestroyed event.
+ *
+ * For a code example and more details, see {@link StreamEvent}.
+ * @name streamDestroyed
+ * @event
+ * @memberof Session
+ * @see StreamEvent
+ */
-/**
- * Defines an event dispatched when property of a stream has changed. This can happen in
- * in the following conditions:
- *
- *
- *
A stream has started or stopped publishing audio or video (see
- * Publisher.publishAudio() and
- * Publisher.publishVideo()). Note
- * that a subscriber's video can be disabled or enabled for reasons other than
- * the publisher disabling or enabling it. A Subscriber object dispatches
- * videoDisabled and videoEnabled events in all
- * conditions that cause the subscriber's stream to be disabled or enabled.
- *
- *
The videoDimensions property of the Stream object has
- * changed (see Stream.videoDimensions).
- *
- *
The videoType property of the Stream object has changed.
- * This can happen in a stream published by a mobile device. (See
- * Stream.videoType.)
- *
A stream has started or stopped publishing audio or video (see
+ * Publisher.publishAudio() and
+ * Publisher.publishVideo()). Note
+ * that a subscriber's video can be disabled or enabled for reasons other than
+ * the publisher disabling or enabling it. A Subscriber object dispatches
+ * videoDisabled and videoEnabled events in all
+ * conditions that cause the subscriber's stream to be disabled or enabled.
+ *
+ *
The videoDimensions property of the Stream object has
+ * changed (see Stream.videoDimensions).
+ *
+ *
The videoType property of the Stream object has changed.
+ * This can happen in a stream published by a mobile device. (See
+ * Stream.videoType.)
+ *
data — (String) The data string sent with the signal (if there
- * is one).
- *
from — (Connection) The Connection
- * corresponding to the client that sent with the signal.
- *
type — (String) The type assigned to the signal (if there is
- * one).
- *
- *
- * You can register to receive all signals sent in the session, by adding an event handler
- * for the signal event. For example, the following code adds an event handler
- * to process all signals sent in the session:
- *
You can register for signals of a specfied type by adding an event handler for the
- * signal:type event (replacing type with the actual type string
- * to filter on).
- *
- * @name signal
- * @event
- * @memberof Session
- * @see Session.signal()
- * @see SignalEvent
- * @see signal:type event
- */
+ /**
+ * A signal was received from the session. The SignalEvent
+ * class defines this event object. It includes the following properties:
+ *
+ *
data — (String) The data string sent with the signal (if there
+ * is one).
+ *
from — (Connection) The Connection
+ * corresponding to the client that sent with the signal.
+ *
type — (String) The type assigned to the signal (if there is
+ * one).
+ *
+ *
+ * You can register to receive all signals sent in the session, by adding an event handler
+ * for the signal event. For example, the following code adds an event handler
+ * to process all signals sent in the session:
+ *
You can register for signals of a specfied type by adding an event handler for the
+ * signal:type event (replacing type with the actual type string
+ * to filter on).
+ *
+ * @name signal
+ * @event
+ * @memberof Session
+ * @see Session.signal()
+ * @see SignalEvent
+ * @see signal:type event
+ */
-/**
- * A signal of the specified type was received from the session. The
- * SignalEvent class defines this event object.
- * It includes the following properties:
- *
- *
data — (String) The data string sent with the signal.
- *
from — (Connection) The Connection
- * corresponding to the client that sent with the signal.
- *
type — (String) The type assigned to the signal (if there is one).
- *
- *
- *
- * You can register for signals of a specfied type by adding an event handler for the
- * signal:type event (replacing type with the actual type string
- * to filter on). For example, the following code adds an event handler for signals of
- * type "foo":
- *
- * session.on("signal:foo", function(event) {
- * console.log("foo signal sent from connection " + event.from.id);
- * console.log("Signal data: " + event.data);
- * });
- *
- *
- * You can register to receive all signals sent in the session, by adding an event
- * handler for the signal event.
- *
- * @name signal:type
- * @event
- * @memberof Session
- * @see Session.signal()
- * @see SignalEvent
- * @see signal event
- */
+ /**
+ * A signal of the specified type was received from the session. The
+ * SignalEvent class defines this event object.
+ * It includes the following properties:
+ *
+ *
data — (String) The data string sent with the signal.
+ *
from — (Connection) The Connection
+ * corresponding to the client that sent with the signal.
+ *
type — (String) The type assigned to the signal (if there is one).
+ *
+ *
+ *
+ * You can register for signals of a specfied type by adding an event handler for the
+ * signal:type event (replacing type with the actual type string
+ * to filter on). For example, the following code adds an event handler for signals of
+ * type "foo":
+ *
+ * session.on("signal:foo", function(event) {
+ * console.log("foo signal sent from connection " + event.from.id);
+ * console.log("Signal data: " + event.data);
+ * });
+ *
+ *
+ * You can register to receive all signals sent in the session, by adding an event
+ * handler for the signal event.
+ *
+ * @name signal:type
+ * @event
+ * @memberof Session
+ * @see Session.signal()
+ * @see SignalEvent
+ * @see signal event
+ */
};
// tb_require('../helpers/helpers.js')
@@ -24751,7 +25565,7 @@ OT.Publisher = function(options) {
_audioLevelMeter,
_properties,
_validResolutions,
- _validFrameRates = [ 1, 7, 15, 30 ],
+ _validFrameRates = [1, 7, 15, 30],
_prevStats,
_state,
_iceServers,
@@ -24765,6 +25579,7 @@ OT.Publisher = function(options) {
options.videoSource === 'application'
),
_connectivityAttemptPinger,
+ _enableSimulcast,
_publisher = this;
_properties = OT.$.defaults(options || {}, {
@@ -24783,21 +25598,13 @@ OT.Publisher = function(options) {
'1280x720': {width: 1280, height: 720}
};
- if (_isScreenSharing) {
- if (window.location.protocol !== 'https:') {
- OT.warn('Screen Sharing typically requires pages to be loadever over HTTPS - unless this ' +
- 'browser is configured locally to allow non-SSL pages, permission will be denied ' +
- 'without user input.');
- }
- }
-
_prevStats = {
- 'timeStamp' : OT.$.now()
+ timeStamp: OT.$.now()
};
OT.$.eventing(this);
- if(!_isScreenSharing && _audioLevelCapable) {
+ if (!_isScreenSharing && _audioLevelCapable) {
_audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext());
var audioLevelRunner = new OT.IntervalRunner(function() {
@@ -24829,10 +25636,10 @@ OT.Publisher = function(options) {
action: action,
variation: variation,
payload: payload,
- 'sessionId': _session ? _session.sessionId : null,
- 'connectionId': _session &&
+ sessionId: _session ? _session.sessionId : null,
+ connectionId: _session &&
_session.isConnected() ? _session.connection.connectionId : null,
- 'partnerId': _session ? _session.apiKey : OT.APIKEY,
+ partnerId: _session ? _session.apiKey : OT.APIKEY,
streamId: _streamId
}, throttle);
},
@@ -24841,10 +25648,10 @@ OT.Publisher = function(options) {
if (variation === 'Attempt' || !_connectivityAttemptPinger) {
_connectivityAttemptPinger = new OT.ConnectivityAttemptPinger({
action: 'Publish',
- 'sessionId': _session ? _session.sessionId : null,
- 'connectionId': _session &&
+ sessionId: _session ? _session.sessionId : null,
+ connectionId: _session &&
_session.isConnected() ? _session.connection.connectionId : null,
- 'partnerId': _session ? _session.apiKey : OT.APIKEY,
+ partnerId: _session ? _session.apiKey : OT.APIKEY,
streamId: _streamId
});
}
@@ -24872,12 +25679,41 @@ OT.Publisher = function(options) {
mediaServerName: _session ? _session.sessionInfo.messagingServer : null,
p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false,
duration: _publishStartTime ? new Date().getTime() - _publishStartTime.getTime() : 0,
- remoteConnectionId: connection.id
+ remoteConnectionId: connection.id,
+ scalableVideo: !!_enableSimulcast
};
- OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) );
+ OT.analytics.logQOS(OT.$.extend(QoSBlob, parsedStats));
this.trigger('qos', parsedStats);
}, this),
+ // Returns the video dimensions. Which could either be the ones that
+ // the developer specific in the videoDimensions property, or just
+ // whatever the video element reports.
+ //
+ // If all else fails then we'll just default to 640x480
+ //
+ getVideoDimensions = function() {
+ var streamWidth;
+ var streamHeight;
+
+ // We set the streamWidth and streamHeight to be the minimum of the requested
+ // resolution and the actual resolution.
+ if (_properties.videoDimensions) {
+ streamWidth = Math.min(_properties.videoDimensions.width,
+ _targetElement.videoWidth() || 640);
+ streamHeight = Math.min(_properties.videoDimensions.height,
+ _targetElement.videoHeight() || 480);
+ } else {
+ streamWidth = _targetElement.videoWidth() || 640;
+ streamHeight = _targetElement.videoHeight() || 480;
+ }
+
+ return {
+ width: streamWidth,
+ height: streamHeight
+ };
+ },
+
/// Private Events
stateChangeFailed = function(changeFailed) {
@@ -24939,7 +25775,6 @@ OT.Publisher = function(options) {
this.publishVideo(_properties.publishVideo &&
_webRTCStream.getVideoTracks().length > 0);
-
this.accessAllowed = true;
this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_ALLOWED, false));
@@ -24959,7 +25794,7 @@ OT.Publisher = function(options) {
onLoaded();
});
- if(_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) {
+ if (_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) {
_audioLevelSampler.webRTCStream(_webRTCStream);
}
@@ -25005,6 +25840,17 @@ OT.Publisher = function(options) {
// The user has clicked the 'deny' button the the allow access dialog
// (or it's set to always deny)
onAccessDenied = OT.$.bind(function(error) {
+ if (_isScreenSharing) {
+ if (window.location.protocol !== 'https:') {
+ /**
+ * in http:// the browser will deny permission without asking the
+ * user. There is also no way to tell if it was denied by the
+ * user, or prevented from the browser.
+ */
+ error.message += ' Note: https:// is required for screen sharing.';
+ }
+ }
+
OT.error('OT.Publisher.onStreamAvailableError Permission Denied');
_state.set('Failed');
@@ -25032,7 +25878,7 @@ OT.Publisher = function(options) {
onAccessDialogClosed = OT.$.bind(function() {
logAnalyticsEvent('accessDialog', 'Closed');
- this.dispatchEvent( new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false));
+ this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false));
}, this),
onVideoError = OT.$.bind(function(errorCode, errorReason) {
@@ -25119,7 +25965,7 @@ OT.Publisher = function(options) {
this.dispatchEvent(new OT.StreamEvent('streamCreated', stream, null, false));
var payload = {
- streamType: 'WebRTC',
+ streamType: 'WebRTC'
};
logConnectivityEvent('Success', payload);
}, this),
@@ -25129,12 +25975,22 @@ OT.Publisher = function(options) {
if (_webRTCStream) {
// Stop revokes our access cam and mic access for this instance
// of localMediaStream.
- _webRTCStream.stop();
+ if (_webRTCStream.stop) {
+ // Older spec
+ _webRTCStream.stop();
+ } else {
+ // Newer spec
+ var tracks = _webRTCStream.getAudioTracks().concat(_webRTCStream.getVideoTracks());
+ tracks.forEach(function(track) {
+ track.stop();
+ });
+ }
+
_webRTCStream = null;
}
},
- createPeerConnectionForRemote = OT.$.bind(function(remoteConnection) {
+ createPeerConnectionForRemote = OT.$.bind(function(remoteConnection, completion) {
var peerConnection = _peerConnections[remoteConnection.id];
if (!peerConnection) {
@@ -25146,12 +26002,42 @@ OT.Publisher = function(options) {
remoteConnection.on('destroyed',
OT.$.bind(this.cleanupSubscriber, this, remoteConnection.id));
+ // Calculate the number of streams to use. 1 for normal, >1 for Simulcast
+ var numberOfSimulcastStreams = 1;
+
+ _enableSimulcast = false;
+ if (OT.$.env.name === 'Chrome' && !_isScreenSharing &&
+ !_session.sessionInfo.p2pEnabled &&
+ _properties.constraints.video) {
+ // We only support simulcast on Chrome, and when not using
+ // screensharing.
+ _enableSimulcast = _session.sessionInfo.simulcast;
+
+ if (_enableSimulcast === void 0) {
+ // If there is no session wide preference then allow the
+ // developer to choose.
+ _enableSimulcast = options && options._enableSimulcast;
+ }
+ }
+
+ if (_enableSimulcast) {
+ var streamDimensions = getVideoDimensions();
+ // HD and above gets three streams. Otherwise they get 2.
+ if (streamDimensions.width > 640 &&
+ streamDimensions.height > 480) {
+ numberOfSimulcastStreams = 3;
+ } else {
+ numberOfSimulcastStreams = 2;
+ }
+ }
+
peerConnection = _peerConnections[remoteConnection.id] = new OT.PublisherPeerConnection(
remoteConnection,
_session,
_streamId,
_webRTCStream,
- _properties.channels
+ _properties.channels,
+ numberOfSimulcastStreams
);
peerConnection.on({
@@ -25167,10 +26053,12 @@ OT.Publisher = function(options) {
qos: recordQOS
}, this);
- peerConnection.init(_iceServers);
+ peerConnection.init(_iceServers, completion);
+ } else {
+ OT.$.callAsync(function() {
+ completion(undefined, peerConnection);
+ });
}
-
- return peerConnection;
}, this),
/// Chrome
@@ -25193,14 +26081,14 @@ OT.Publisher = function(options) {
updateChromeForStyleChange = function(key, value) {
if (!_chrome) return;
- switch(key) {
+ switch (key) {
case 'nameDisplayMode':
_chrome.name.setDisplayMode(value);
_chrome.backingBar.setNameMode(value);
break;
case 'showArchiveStatus':
- logAnalyticsEvent('showArchiveStatus', 'styleChange', {mode: value ? 'on': 'off'});
+ logAnalyticsEvent('showArchiveStatus', 'styleChange', {mode: value ? 'on' : 'off'});
_chrome.archive.setShowArchiveStatus(value);
break;
@@ -25220,7 +26108,7 @@ OT.Publisher = function(options) {
createChrome = function() {
- if(!this.getStyle('showArchiveStatus')) {
+ if (!this.getStyle('showArchiveStatus')) {
logAnalyticsEvent('showArchiveStatus', 'createChrome', {mode: 'off'});
}
@@ -25273,9 +26161,7 @@ OT.Publisher = function(options) {
unmuted: OT.$.bind(this.publishAudio, this, true)
});
- if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') {
- _audioLevelMeter[_widgetView.audioOnly() ? 'show' : 'hide']();
- }
+ updateAudioLevelMeterDisplay();
},
reset = OT.$.bind(function() {
@@ -25319,25 +26205,35 @@ OT.Publisher = function(options) {
buttonDisplayMode: 'auto',
audioLevelDisplayMode: _isScreenSharing ? 'off' : 'auto',
backgroundImageURI: null
- }, _properties.showControls, function (payload) {
+ }, _properties.showControls, function(payload) {
logAnalyticsEvent('SetStyle', 'Publisher', payload, 0.1);
});
+ var updateAudioLevelMeterDisplay = function() {
+ if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') {
+ if (!_properties.publishVideo && _properties.publishAudio) {
+ _audioLevelMeter.show();
+ } else {
+ _audioLevelMeter.hide();
+ }
+ }
+ };
+
var setAudioOnly = function(audioOnly) {
if (_widgetView) {
_widgetView.audioOnly(audioOnly);
_widgetView.showPoster(audioOnly);
}
- if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') {
- _audioLevelMeter[audioOnly ? 'show' : 'hide']();
- }
+ updateAudioLevelMeterDisplay();
};
this.publish = function(targetElement) {
OT.debug('OT.Publisher: publish');
- if ( _state.isAttemptingToPublish() || _state.isPublishing() ) reset();
+ if (_state.isAttemptingToPublish() || _state.isPublishing()) {
+ reset();
+ }
_state.set('GetUserMedia');
if (!_properties.constraints) {
@@ -25351,12 +26247,12 @@ OT.Publisher = function(options) {
_properties.audioSource = null;
}
- if(_properties.audioSource === null || _properties.audioSource === false) {
+ if (_properties.audioSource === null || _properties.audioSource === false) {
_properties.constraints.audio = false;
_properties.publishAudio = false;
} else {
- if(typeof _properties.audioSource === 'object') {
- if(_properties.audioSource.deviceId != null) {
+ if (typeof _properties.audioSource === 'object') {
+ if (_properties.audioSource.deviceId != null) {
_properties.audioSource = _properties.audioSource.deviceId;
} else {
OT.warn('Invalid audioSource passed to Publisher. Expected either a device ID');
@@ -25378,12 +26274,12 @@ OT.Publisher = function(options) {
}
}
- if(_properties.videoSource === null || _properties.videoSource === false) {
+ if (_properties.videoSource === null || _properties.videoSource === false) {
_properties.constraints.video = false;
_properties.publishVideo = false;
} else {
- if(typeof _properties.videoSource === 'object' &&
+ if (typeof _properties.videoSource === 'object' &&
_properties.videoSource.deviceId == null) {
OT.warn('Invalid videoSource passed to Publisher. Expected either a device ' +
'ID or device.');
@@ -25407,12 +26303,13 @@ OT.Publisher = function(options) {
var mandatory = _properties.constraints.video.mandatory;
- if(_isScreenSharing) {
- // this is handled by the extension helpers
- } else if(_properties.videoSource.deviceId != null) {
- mandatory.sourceId = _properties.videoSource.deviceId;
- } else {
- mandatory.sourceId = _properties.videoSource;
+ // _isScreenSharing is handled by the extension helpers
+ if (!_isScreenSharing) {
+ if (_properties.videoSource.deviceId != null) {
+ mandatory.sourceId = _properties.videoSource.deviceId;
+ } else {
+ mandatory.sourceId = _properties.videoSource;
+ }
}
}
@@ -25432,9 +26329,8 @@ OT.Publisher = function(options) {
{minHeight: _properties.videoDimensions.height},
{maxHeight: _properties.videoDimensions.height}
]);
- } else {
- // This is not supported
}
+ // We do not support this in Firefox yet
}
}
@@ -25459,9 +26355,8 @@ OT.Publisher = function(options) {
_properties.videoDimensions.width;
_properties.constraints.video.mandatory.maxHeight =
_properties.videoDimensions.height;
- } else {
- // This is not suppoted
}
+ // We do not support this in Firefox yet
}
if (_properties.frameRate !== void 0 &&
@@ -25514,7 +26409,7 @@ OT.Publisher = function(options) {
var event = new OT.MediaStoppedEvent(_publisher);
_publisher.dispatchEvent(event, function() {
- if(!event.isDefaultPrevented()) {
+ if (!event.isDefaultPrevented()) {
if (_session) {
_publisher._.unpublishFromSession(_session, 'mediaStopped');
} else {
@@ -25534,12 +26429,12 @@ OT.Publisher = function(options) {
);
} else if (response.extensionRegistered === false) {
onScreenSharingError(
- new Error('Screen Sharing suppor in this browser requires an extension, but ' +
+ new Error('Screen Sharing support in this browser requires an extension, but ' +
'one has not been registered.')
);
} else if (response.extensionInstalled === false) {
onScreenSharingError(
- new Error('Screen Sharing suppor in this browser requires an extension, but ' +
+ new Error('Screen Sharing support in this browser requires an extension, but ' +
'the extension is not installed.')
);
} else {
@@ -25570,11 +26465,11 @@ OT.Publisher = function(options) {
});
} else {
OT.$.shouldAskForDevices(function(devices) {
- if(!devices.video) {
+ if (!devices.video) {
OT.warn('Setting video constraint to false, there are no video sources');
_properties.constraints.video = false;
}
- if(!devices.audio) {
+ if (!devices.audio) {
OT.warn('Setting audio constraint to false, there are no audio sources');
_properties.constraints.audio = false;
}
@@ -25606,19 +26501,19 @@ OT.Publisher = function(options) {
return this;
};
-/**
-* Starts publishing audio (if it is currently not being published)
-* when the value is true; stops publishing audio
-* (if it is currently being published) when the value is false.
-*
-* @param {Boolean} value Whether to start publishing audio (true)
-* or not (false).
-*
-* @see OT.initPublisher()
-* @see Stream.hasAudio
-* @see StreamPropertyChangedEvent
-* @method #publishAudio
-* @memberOf Publisher
+ /**
+ * Starts publishing audio (if it is currently not being published)
+ * when the value is true; stops publishing audio
+ * (if it is currently being published) when the value is false.
+ *
+ * @param {Boolean} value Whether to start publishing audio (true)
+ * or not (false).
+ *
+ * @see OT.initPublisher()
+ * @see Stream.hasAudio
+ * @see StreamPropertyChangedEvent
+ * @method #publishAudio
+ * @memberOf Publisher
*/
this.publishAudio = function(value) {
_properties.publishAudio = value;
@@ -25635,23 +26530,24 @@ OT.Publisher = function(options) {
_stream.setChannelActiveState('audio', value);
}
+ updateAudioLevelMeterDisplay();
+
return this;
};
-
-/**
-* Starts publishing video (if it is currently not being published)
-* when the value is true; stops publishing video
-* (if it is currently being published) when the value is false.
-*
-* @param {Boolean} value Whether to start publishing video (true)
-* or not (false).
-*
-* @see OT.initPublisher()
-* @see Stream.hasVideo
-* @see StreamPropertyChangedEvent
-* @method #publishVideo
-* @memberOf Publisher
+ /**
+ * Starts publishing video (if it is currently not being published)
+ * when the value is true; stops publishing video
+ * (if it is currently being published) when the value is false.
+ *
+ * @param {Boolean} value Whether to start publishing video (true)
+ * or not (false).
+ *
+ * @see OT.initPublisher()
+ * @see Stream.hasVideo
+ * @see StreamPropertyChangedEvent
+ * @method #publishVideo
+ * @memberOf Publisher
*/
this.publishVideo = function(value) {
var oldValue = _properties.publishVideo;
@@ -25666,7 +26562,7 @@ OT.Publisher = function(options) {
// the value of publishVideo at this point. This will be tidied up shortly.
if (_webRTCStream) {
var videoTracks = _webRTCStream.getVideoTracks();
- for (var i=0, num=videoTracks.length; iaccessAllowed property which indicates whether the user
-* has granted access to the camera and microphone.
-* @see Event
-* @name accessAllowed
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the user has clicked the Allow button, granting the
+ * app access to the camera and microphone. The Publisher object has an
+ * accessAllowed property which indicates whether the user
+ * has granted access to the camera and microphone.
+ * @see Event
+ * @name accessAllowed
+ * @event
+ * @memberof Publisher
*/
-/**
-* Dispatched when the user has clicked the Deny button, preventing the
-* app from having access to the camera and microphone.
-* @see Event
-* @name accessDenied
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the user has clicked the Deny button, preventing the
+ * app from having access to the camera and microphone.
+ * @see Event
+ * @name accessDenied
+ * @event
+ * @memberof Publisher
*/
-/**
-* Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which
-* the user can grant the app access to the camera and microphone.)
-* @see Event
-* @name accessDialogOpened
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which
+ * the user can grant the app access to the camera and microphone.)
+ * @see Event
+ * @name accessDialogOpened
+ * @event
+ * @memberof Publisher
*/
-/**
-* Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the
-* user can grant the app access to the camera and microphone.)
-* @see Event
-* @name accessDialogClosed
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the
+ * user can grant the app access to the camera and microphone.)
+ * @see Event
+ * @name accessDialogClosed
+ * @event
+ * @memberof Publisher
*/
/**
@@ -26197,90 +27095,58 @@ OT.Publisher = function(options) {
* @see AudioLevelUpdatedEvent
*/
-/**
- * The publisher has started streaming to the session.
- * @name streamCreated
- * @event
- * @memberof Publisher
- * @see StreamEvent
- * @see Session.publish()
- */
+ /**
+ * The publisher has started streaming to the session.
+ * @name streamCreated
+ * @event
+ * @memberof Publisher
+ * @see StreamEvent
+ * @see Session.publish()
+ */
-/**
- * The publisher has stopped streaming to the session. The default behavior is that
- * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a
- * destroyed event when the element is removed from the HTML DOM. If you call the
- * preventDefault() method of the event object in the event listener, the default
- * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up
- * using your own code.
- * @name streamDestroyed
- * @event
- * @memberof Publisher
- * @see StreamEvent
- */
+ /**
+ * The publisher has stopped streaming to the session. The default behavior is that
+ * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a
+ * destroyed event when the element is removed from the HTML DOM. If you call the
+ * preventDefault() method of the event object in the event listener, the default
+ * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up
+ * using your own code.
+ * @name streamDestroyed
+ * @event
+ * @memberof Publisher
+ * @see StreamEvent
+ */
-/**
-* Dispatched when the Publisher element is removed from the HTML DOM. When this event
-* is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher.
-* @name destroyed
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the Publisher element is removed from the HTML DOM. When this event
+ * is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher.
+ * @name destroyed
+ * @event
+ * @memberof Publisher
*/
-/**
-* Dispatched when the video dimensions of the video change. This can only occur in when the
-* stream.videoType property is set to "screen" (for a screen-sharing
-* video stream), and the user resizes the window being captured.
-* @name videoDimensionsChanged
-* @event
-* @memberof Publisher
+ /**
+ * Dispatched when the video dimensions of the video change. This can only occur in when the
+ * stream.videoType property is set to "screen" (for a screen-sharing
+ * video stream), and the user resizes the window being captured.
+ * @name videoDimensionsChanged
+ * @event
+ * @memberof Publisher
*/
-/**
- * The user has stopped screen-sharing for the published stream. This event is only dispatched
- * for screen-sharing video streams.
- * @name mediaStopped
- * @event
- * @memberof Publisher
- * @see StreamEvent
- */
+ /**
+ * The user has stopped screen-sharing for the published stream. This event is only dispatched
+ * for screen-sharing video streams.
+ * @name mediaStopped
+ * @event
+ * @memberof Publisher
+ * @see StreamEvent
+ */
};
// Helper function to generate unique publisher ids
OT.Publisher.nextId = OT.$.uuid;
-// tb_require('./helpers/lib/css_loader.js')
-// tb_require('./ot/system_requirements.js')
-// tb_require('./ot/session.js')
-// tb_require('./ot/publisher.js')
-// tb_require('./ot/subscriber.js')
-// tb_require('./ot/archive.js')
-// tb_require('./ot/connection.js')
-// tb_require('./ot/stream.js')
-// We want this to be included at the end, just before footer.js
-
-/* jshint globalstrict: true, strict: false, undef: true, unused: true,
- trailing: true, browser: true, smarttabs:true */
-/* global loadCSS, define */
-
-// Tidy up everything on unload
-OT.onUnload(function() {
- OT.publishers.destroy();
- OT.subscribers.destroy();
- OT.sessions.destroy('unloaded');
-});
-
-loadCSS(OT.properties.cssURL);
-
-// Register as a named AMD module, since TokBox could be concatenated with other
-// files that may use define, but not via a proper concatenation script that
-// understands anonymous AMD modules. A named AMD is safest and most robust
-// way to register. Uppercase TB is used because AMD module names are
-// derived from file names, and OpenTok is normally delivered in an uppercase
-// file name.
-if (typeof define === 'function' && define.amd) {
- define( 'TB', [], function () { return TB; } );
-}
// tb_require('../../conf/properties.js')
// tb_require('../ot.js')
// tb_require('./session.js')
@@ -26290,7 +27156,6 @@ if (typeof define === 'function' && define.amd) {
trailing: true, browser: true, smarttabs:true */
/* global OT */
-
/**
* The first step in using the OpenTok API is to call the OT.initSession()
* method. Other methods of the OT object check for system requirements and set up error logging.
@@ -26319,13 +27184,14 @@ if (typeof define === 'function' && define.amd) {
* @param {String} apiKey Your OpenTok API key (see the
* OpenTok dashboard).
* @param {String} sessionId The session ID identifying the OpenTok session. For more
-* information, see Session creation.
+* information, see Session
+* creation.
* @returns {Session} The session object through which all further interactions with
* the session will occur.
*/
OT.initSession = function(apiKey, sessionId) {
- if(sessionId == null) {
+ if (sessionId == null) {
sessionId = apiKey;
apiKey = null;
}
@@ -26444,16 +27310,21 @@ OT.initSession = function(apiKey, sessionId) {
*
*
*
-* height (Number) The desired height, in pixels, of the
-* displayed Publisher video stream (default: 198). Note: Use the
-* height and width properties to set the dimensions
-* of the publisher video; do not set the height and width of the DOM element
-* (using CSS).
+* height (Number) The desired initial height of the displayed Publisher
+* video in the HTML page (default: 198 pixels). You can specify the number of pixels as either
+* a number (such as 300) or a string ending in "px" (such as "300px"). Or you can specify a
+* percentage of the size of the parent element, with a string ending in "%" (such as "100%").
+* Note: To resize the publisher video, adjust the CSS of the publisher's DOM element
+* (the element property of the Publisher object) or (if the height is specified as
+* a percentage) its parent DOM element (see
+* Resizing
+* or repositioning a video).
*
*
* insertMode (String) Specifies how the Publisher object will be
* inserted in the HTML DOM. See the targetElement parameter. This string can
* have the following values:
+*
*
*
"replace" The Publisher object replaces contents of the
* targetElement. This is the default.
"append" The Publisher object is a new element added as a child
* of the targetElement. If there are other child elements, the Publisher is appended as
* the last child element of the targetElement.
-*
+*
+*
Do not move the publisher element or its parent elements in the DOM
+* heirarchy. Use CSS to resize or reposition the publisher video's element
+* (the element property of the Publisher object) or its parent element (see
+* Resizing
+* or repositioning a video.
*
*
* maxResolution (Object) Sets the maximum resoultion to stream.
@@ -26550,7 +27426,7 @@ OT.initSession = function(apiKey, sessionId) {
* video is disabled), "off" (the indicator is not displayed), and
* "on" (the indicator is always displayed).
*
-*
backgroundImageURI (String) — A URI for an image to display as
+*
backgroundImageURI (String) — A URI for an image to display as
* the background image when a video is not displayed. (A video may not be displayed if
* you call publishVideo(false) on the Publisher object). You can pass an http
* or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the
@@ -26560,14 +27436,7 @@ OT.initSession = function(apiKey, sessionId) {
* you could set the property to "data:VBORw0KGgoAA...", where the portion of the
* string after "data:" is the result of a call to
* Publisher.getImgData(). If the URL or the image data is invalid, the property
-* is ignored (the attempt to set the image fails silently).
-*
-* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer),
-* you cannot set the backgroundImageURI style to a string larger than 32 kB.
-* This is due to an IE 8 limitation on the size of URI strings. Due to this limitation,
-* you cannot set the backgroundImageURI style to a string obtained with the
-* getImgData() method.
-*
+* is ignored (the attempt to set the image fails silently).
*
*
buttonDisplayMode (String) — How to display the microphone controls
* Possible values are: "auto" (controls are displayed when the stream is first
@@ -26604,11 +27473,15 @@ OT.initSession = function(apiKey, sessionId) {
* subscribers to the stream is "fit".
*
*
-* width (Number) The desired width, in pixels, of the
-* displayed Publisher video stream (default: 264). Note: Use the
-* height and width properties to set the dimensions
-* of the publisher video; do not set the height and width of the DOM element
-* (using CSS).
+* width (Number) The desired initial width of the displayed Publisher
+* video in the HTML page (default: 264 pixels). You can specify the number of pixels as either
+* a number (such as 400) or a string ending in "px" (such as "400px"). Or you can specify a
+* percentage of the size of the parent element, with a string ending in "%" (such as "100%").
+* Note: To resize the publisher video, adjust the CSS of the publisher's DOM element
+* (the element property of the Publisher object) or (if the width is specified as
+* a percentage) its parent DOM element (see
+* Resizing
+* or repositioning a video).
*
*
* @param {Function} completionHandler (Optional) A function to be called when the method succeeds
@@ -26640,28 +27513,27 @@ OT.initSession = function(apiKey, sessionId) {
* @memberof OT
*/
OT.initPublisher = function(targetElement, properties, completionHandler) {
- OT.debug('OT.initPublisher('+targetElement+')');
+ OT.debug('OT.initPublisher(' + targetElement + ')');
// To support legacy (apikey, targetElement, properties) users
// we check to see if targetElement is actually an apikey. Which we ignore.
- if(typeof targetElement === 'string' && !document.getElementById(targetElement)) {
+ if (typeof targetElement === 'string' && !document.getElementById(targetElement)) {
targetElement = properties;
properties = completionHandler;
completionHandler = arguments[3];
}
- if(typeof targetElement === 'function') {
+ if (typeof targetElement === 'function') {
completionHandler = targetElement;
properties = undefined;
targetElement = undefined;
- }
- else if (OT.$.isObject(targetElement) && !(OT.$.isElementNode(targetElement))) {
+ } else if (OT.$.isObject(targetElement) && !(OT.$.isElementNode(targetElement))) {
completionHandler = properties;
properties = targetElement;
targetElement = undefined;
}
- if(typeof properties === 'function') {
+ if (typeof properties === 'function') {
completionHandler = properties;
properties = undefined;
}
@@ -26669,20 +27541,18 @@ OT.initPublisher = function(targetElement, properties, completionHandler) {
var publisher = new OT.Publisher(properties);
OT.publishers.add(publisher);
- var triggerCallback = function triggerCallback (err) {
- if (err) {
- OT.dispatchError(err.code, err.message, completionHandler, publisher.session);
- } else if (completionHandler && OT.$.isFunction(completionHandler)) {
+ var triggerCallback = function triggerCallback() {
+ if (completionHandler && OT.$.isFunction(completionHandler)) {
completionHandler.apply(null, arguments);
}
},
- removeInitSuccessAndCallComplete = function removeInitSuccessAndCallComplete (err) {
+ removeInitSuccessAndCallComplete = function removeInitSuccessAndCallComplete(err) {
publisher.off('publishComplete', removeHandlersAndCallComplete);
triggerCallback(err);
},
- removeHandlersAndCallComplete = function removeHandlersAndCallComplete (err) {
+ removeHandlersAndCallComplete = function removeHandlersAndCallComplete(err) {
publisher.off('initSuccess', removeInitSuccessAndCallComplete);
// We're only handling the error case here as we're just
@@ -26690,7 +27560,6 @@ OT.initPublisher = function(targetElement, properties, completionHandler) {
if (err) triggerCallback(err);
};
-
publisher.once('initSuccess', removeInitSuccessAndCallComplete);
publisher.once('publishComplete', removeHandlersAndCallComplete);
@@ -26699,7 +27568,6 @@ OT.initPublisher = function(targetElement, properties, completionHandler) {
return publisher;
};
-
/**
* Enumerates the audio input devices (such as microphones) and video input devices
* (cameras) available to the browser.
@@ -26707,8 +27575,8 @@ OT.initPublisher = function(targetElement, properties, completionHandler) {
* The array of devices is passed in as the devices parameter of
* the callback function passed into the method.
*
- * This method is only available in Chrome. In other browsers, the callback function is
- * passed an error object.
+ * This method is only available in Chrome and Internet Explorer. In Firefox, the callback function
+ * is passed an error object.
*
* @param callback {Function} The callback function invoked when the list of devices
* devices is available. This function takes two parameters:
@@ -26746,15 +27614,65 @@ OT.getDevices = function(callback) {
OT.$.getMediaDevices(callback);
};
+/**
+* Report that your app experienced an issue. You can use the issue ID with
+* Inspector or when discussing
+* an issue with the TokBox support team.
+*
+* @param completionHandler {Function} A function that is called when the call to this method
+* succeeds or fails. This function has two parameters. The first parameter is an
+* Error object that is set when the call to the reportIssue()
+* method fails (for example, if the client is not connected to the network) or null
+* when the call to the reportIssue() method succeeds. The second parameter is set to
+* the report ID (a unique string) when the call succeeds.
+*
+* @method OT.reportIssue
+* @memberof OT
+*/
+OT.reportIssue = function(completionHandler) {
+ var reportIssueId = OT.$.uuid();
+ var sessionCount = OT.sessions.length();
+ var completedLogEventCount = 0;
+ var errorReported = false;
+ function logEventCompletionHandler(error) {
+ if (error) {
+ if (completionHandler && !errorReported) {
+ var reportIssueError = new OT.Error(OT.ExceptionCodes.REPORT_ISSUE_ERROR,
+ 'Error calling OT.reportIssue(). Check the client\'s network connection.');
+ completionHandler(reportIssueError);
+ errorReported = true;
+ }
+ } else {
+ completedLogEventCount++;
+ if (completedLogEventCount >= sessionCount && completionHandler && !errorReported) {
+ completionHandler(null, reportIssueId);
+ }
+ }
+ }
-OT.reportIssue = function(){
- OT.warn('ToDo: haven\'t yet implemented OT.reportIssue');
+ var eventOptions = {
+ action: 'ReportIssue',
+ payload: {
+ reportIssueId: reportIssueId
+ }
+ };
+
+ if (sessionCount === 0) {
+ OT.analytics.logEvent(eventOptions, null, logEventCompletionHandler);
+ } else {
+ OT.sessions.forEach(function(session) {
+ var individualSessionEventOptions = OT.$.extend({
+ sessionId: session.sessionId,
+ partnerId: session.isConnected() ? session.sessionInfo.partnerId : null
+ }, eventOptions);
+ OT.analytics.logEvent(individualSessionEventOptions, null, logEventCompletionHandler);
+ });
+ }
};
OT.components = {};
-
/**
* This method is deprecated. Use on() or once() instead.
*
@@ -26813,7 +27731,6 @@ OT.components = {};
* @method removeEventListener
*/
-
/**
* Adds an event handler function for one or more events.
*
@@ -26924,7 +27841,6 @@ OT.components = {};
* @see Events
*/
-
/**
* Removes an event handler.
*
@@ -26979,6 +27895,38 @@ OT.components = {};
* @see ExceptionEvent
*/
+// tb_require('./helpers/lib/css_loader.js')
+// tb_require('./ot/system_requirements.js')
+// tb_require('./ot/session.js')
+// tb_require('./ot/publisher.js')
+// tb_require('./ot/subscriber.js')
+// tb_require('./ot/archive.js')
+// tb_require('./ot/connection.js')
+// tb_require('./ot/stream.js')
+// We want this to be included at the end, just before footer.js
+
+/* jshint globalstrict: true, strict: false, undef: true, unused: true,
+ trailing: true, browser: true, smarttabs:true */
+/* global loadCSS, define */
+
+// Tidy up everything on unload
+OT.onUnload(function() {
+ OT.publishers.destroy();
+ OT.subscribers.destroy();
+ OT.sessions.destroy('unloaded');
+});
+
+loadCSS(OT.properties.cssURL);
+
+// Register as a named AMD module, since TokBox could be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Uppercase TB is used because AMD module names are
+// derived from file names, and OpenTok is normally delivered in an uppercase
+// file name.
+if (typeof define === 'function' && define.amd) {
+ define('TB', [], function() { return TB; });
+}
// tb_require('./postscript.js')
From 974f3affa34777465f61fe4ab1d9e7498873e517 Mon Sep 17 00:00:00 2001
From: Mark Banner
Date: Tue, 10 Nov 2015 08:58:10 +0000
Subject: [PATCH 03/71] Bug 1210865 - Change how Loop's data channels are setup
to cope with the newer SDK that doesn't allow setting them up until
subscription is complete. r=dmose
---
.../loop/content/shared/js/otSdkDriver.js | 97 +++---
.../test/functional/test_1_browser_call.py | 47 +++
.../loop/test/shared/otSdkDriver_test.js | 328 ++++++++++--------
3 files changed, 284 insertions(+), 188 deletions(-)
diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js
index 6555b157e22..4fdb1d7bd03 100644
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -620,7 +620,7 @@ loop.OTSdkDriver = (function() {
this.dispatcher.dispatch(new sharedActions.MediaConnected());
}
- this._setupDataChannelIfNeeded(sdkSubscriberObject.stream.connection);
+ this._setupDataChannelIfNeeded(sdkSubscriberObject);
},
/**
@@ -655,61 +655,24 @@ loop.OTSdkDriver = (function() {
* channel set-up routines. A data channel cannot be requested before this
* time as the peer connection is not set up.
*
- * @param {OT.Connection} connection The OT connection class object.paul
- * sched
+ * @param {OT.Subscriber} sdkSubscriberObject The subscriber object for the stream.
*
*/
- _setupDataChannelIfNeeded: function(connection) {
- if (this._useDataChannels) {
- this.session.signal({
- type: "readyForDataChannel",
- to: connection
- }, function(signalError) {
- if (signalError) {
- console.error(signalError);
- }
- });
- }
- },
-
- /**
- * Handles receiving the signal that the other end of the connection
- * has subscribed to the stream and we're ready to setup the data channel.
- *
- * We get data channels for both the publisher and subscriber on reception
- * of the signal, as it means that a) the remote client is setup for data
- * channels, and b) that subscribing of streams has definitely completed
- * for both clients.
- *
- * @param {OT.SignalEvent} event Details of the signal received.
- */
- _onReadyForDataChannel: function(event) {
- // If we don't want data channels, just ignore the message. We haven't
- // send the other side a message, so it won't display anything.
+ _setupDataChannelIfNeeded: function(sdkSubscriberObject) {
if (!this._useDataChannels) {
return;
}
- // This won't work until a subscriber exists for this publisher
- this.publisher._.getDataChannel("text", {}, function(err, channel) {
- if (err) {
- console.error(err);
- return;
+ this.session.signal({
+ type: "readyForDataChannel",
+ to: sdkSubscriberObject.stream.connection
+ }, function(signalError) {
+ if (signalError) {
+ console.error(signalError);
}
+ });
- this._publisherChannel = channel;
-
- channel.on({
- close: function(e) {
- // XXX We probably want to dispatch and handle this somehow.
- console.log("Published data channel closed!");
- }
- });
-
- this._checkDataChannelsAvailable();
- }.bind(this));
-
- this.subscriber._.getDataChannel("text", {}, function(err, channel) {
+ sdkSubscriberObject._.getDataChannel("text", {}, function(err, channel) {
// Sends will queue until the channel is fully open.
if (err) {
console.error(err);
@@ -741,6 +704,44 @@ loop.OTSdkDriver = (function() {
}.bind(this));
},
+ /**
+ * Handles receiving the signal that the other end of the connection
+ * has subscribed to the stream and we're ready to setup the data channel.
+ *
+ * We create the publisher data channel when we get the signal as it means
+ * that the remote client is setup for data
+ * channels. Getting the data channel for the subscriber is handled
+ * separately when the subscription completes.
+ *
+ * @param {OT.SignalEvent} event Details of the signal received.
+ */
+ _onReadyForDataChannel: function(event) {
+ // If we don't want data channels, just ignore the message. We haven't
+ // send the other side a message, so it won't display anything.
+ if (!this._useDataChannels) {
+ return;
+ }
+
+ // This won't work until a subscriber exists for this publisher
+ this.publisher._.getDataChannel("text", {}, function(err, channel) {
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ this._publisherChannel = channel;
+
+ channel.on({
+ close: function(e) {
+ // XXX We probably want to dispatch and handle this somehow.
+ console.log("Published data channel closed!");
+ }
+ });
+
+ this._checkDataChannelsAvailable();
+ }.bind(this));
+ },
+
/**
* Checks to see if all channels have been obtained, and if so it dispatches
* a notification to the stores to inform them.
diff --git a/browser/components/loop/test/functional/test_1_browser_call.py b/browser/components/loop/test/functional/test_1_browser_call.py
index 8238716136d..de09997d175 100644
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -142,6 +142,50 @@ class Test1BrowserCall(MarionetteTestCase):
self.switch_to_chatbox()
self.check_video(".remote-video")
+ def send_chat_message(self, text):
+ """
+ Sends a chat message using the current context.
+
+ :param text: The text to send.
+ """
+ chatbox = self.wait_for_element_displayed(By.CSS_SELECTOR,
+ ".text-chat-box > form > input")
+
+ chatbox.send_keys(text + "\n")
+
+ def check_received_message(self, expectedText):
+ """
+ Checks a chat message has been received in the current context. The
+ test assumes only one chat message will be received during the tests.
+
+ :param expectedText: The expected text of the chat message.
+ """
+ text_entry = self.wait_for_element_displayed(By.CSS_SELECTOR,
+ ".text-chat-entry.received > p > span")
+
+ self.assertEqual(text_entry.text, expectedText,
+ "should have received the correct message")
+
+ def check_text_messaging(self):
+ """
+ Checks text messaging between the generator and clicker in a bi-directional
+ fashion.
+ """
+ # Send a message using the link generator.
+ self.switch_to_chatbox()
+ self.send_chat_message("test1")
+
+ # Now check the result on the link clicker.
+ self.switch_to_standalone()
+ self.check_received_message("test1")
+
+ # Then send a message using the standalone.
+ self.send_chat_message("test2")
+
+ # Finally check the link generator got it.
+ self.switch_to_chatbox()
+ self.check_received_message("test2")
+
def local_enable_screenshare(self):
self.switch_to_chatbox()
button = self.marionette.find_element(By.CLASS_NAME, "btn-screen-share")
@@ -242,6 +286,9 @@ class Test1BrowserCall(MarionetteTestCase):
self.standalone_check_remote_video()
self.local_check_remote_video()
+ # Check text messaging
+ self.check_text_messaging()
+
# since bi-directional media is connected, make sure we've set
# the start time
self.local_check_media_start_time_initialized()
diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js
index 0993bd10ed8..d9bc3b8510f 100644
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -752,6 +752,9 @@ describe("loop.OTSdkDriver", function() {
};
fakeSubscriberObject = _.extend({
+ "_": {
+ getDataChannel: sinon.stub()
+ },
session: { connection: fakeConnection },
stream: fakeStream
}, Backbone.Events);
@@ -996,131 +999,196 @@ describe("loop.OTSdkDriver", function() {
}));
});
- it("should subscribe to a camera stream", function() {
- session.trigger("streamCreated", { stream: fakeStream });
+ describe("Audio/Video streams", function() {
+ beforeEach(function() {
+ session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
+ videoElement).returns(this.fakeSubscriberObject);
+ });
- sinon.assert.calledOnce(session.subscribe);
- sinon.assert.calledWithExactly(session.subscribe,
- fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig,
- sinon.match.func);
- });
+ it("should subscribe to a camera stream", function() {
+ session.trigger("streamCreated", { stream: fakeStream });
- it("should dispatch MediaStreamCreated after subscribe is complete", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- videoElement).returns(this.fakeSubscriberObject);
- driver.session = session;
- fakeStream.connection = fakeConnection;
- fakeStream.hasVideo = true;
+ sinon.assert.calledOnce(session.subscribe);
+ sinon.assert.calledWithExactly(session.subscribe,
+ fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig,
+ sinon.match.func);
+ });
- session.trigger("streamCreated", { stream: fakeStream });
+ it("should dispatch MediaStreamCreated after streamCreated is triggered on the session", function() {
+ driver.session = session;
+ fakeStream.connection = fakeConnection;
+ fakeStream.hasVideo = true;
- sinon.assert.called(dispatcher.dispatch);
- sinon.assert.calledWithExactly(dispatcher.dispatch,
- new sharedActions.MediaStreamCreated({
- hasVideo: true,
- isLocal: false,
- srcMediaElement: videoElement
- }));
- });
+ session.trigger("streamCreated", { stream: fakeStream });
- it("should dispatch MediaStreamCreated after subscribe with audio-only indication if hasVideo=false", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- videoElement);
- fakeStream.connection = fakeConnection;
- fakeStream.hasVideo = false;
+ sinon.assert.called(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
+ new sharedActions.MediaStreamCreated({
+ hasVideo: true,
+ isLocal: false,
+ srcMediaElement: videoElement
+ }));
+ });
- session.trigger("streamCreated", { stream: fakeStream });
+ it("should dispatch MediaStreamCreated after streamCreated with audio-only indication if hasVideo=false", function() {
+ fakeStream.connection = fakeConnection;
+ fakeStream.hasVideo = false;
- sinon.assert.called(dispatcher.dispatch);
- sinon.assert.calledWithExactly(dispatcher.dispatch,
- new sharedActions.MediaStreamCreated({
- hasVideo: false,
- isLocal: false,
- srcMediaElement: videoElement
- }));
- });
+ session.trigger("streamCreated", { stream: fakeStream });
- it("should trigger a readyForDataChannel signal after subscribe is complete", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- document.createElement("video"));
- driver._useDataChannels = true;
- fakeStream.connection = fakeConnection;
+ sinon.assert.called(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
+ new sharedActions.MediaStreamCreated({
+ hasVideo: false,
+ isLocal: false,
+ srcMediaElement: videoElement
+ }));
+ });
- session.trigger("streamCreated", { stream: fakeStream });
+ it("should dispatch a mediaConnected action if both streams are up", function() {
+ driver._publishedLocalStream = true;
- sinon.assert.calledOnce(session.signal);
- sinon.assert.calledWith(session.signal, {
- type: "readyForDataChannel",
- to: fakeConnection
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ // Called twice due to the VideoDimensionsChanged above.
+ sinon.assert.called(dispatcher.dispatch);
+ sinon.assert.calledWithMatch(dispatcher.dispatch,
+ new sharedActions.MediaConnected({}));
+ });
+
+ it("should store the start time when both streams are up and" +
+ " driver._sendTwoWayMediaTelemetry is true", function() {
+ driver._sendTwoWayMediaTelemetry = true;
+ driver._publishedLocalStream = true;
+ var startTime = 1;
+ sandbox.stub(performance, "now").returns(startTime);
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ expect(driver._getTwoWayMediaStartTime()).to.eql(startTime);
+ });
+
+ it("should not store the start time when both streams are up and" +
+ " driver._isDesktop is false", function() {
+ driver._isDesktop = false;
+ driver._publishedLocalStream = true;
+ var startTime = 73;
+ sandbox.stub(performance, "now").returns(startTime);
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime);
+ });
+
+ describe("Data channel setup", function() {
+ var fakeChannel;
+
+ beforeEach(function() {
+ fakeChannel = _.extend({}, Backbone.Events);
+ fakeStream.connection = fakeConnection;
+ driver._useDataChannels = true;
+ });
+
+ it("should trigger a readyForDataChannel signal after subscribe is complete", function() {
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.calledOnce(session.signal);
+ sinon.assert.calledWith(session.signal, {
+ type: "readyForDataChannel",
+ to: fakeConnection
+ });
+ });
+
+ it("should not trigger readyForDataChannel signal if data channels are not wanted", function() {
+ driver._useDataChannels = false;
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.notCalled(session.signal);
+ });
+
+ it("should get the data channel after subscribe is complete", function() {
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.calledOnce(fakeSubscriberObject._.getDataChannel);
+ sinon.assert.calledWith(fakeSubscriberObject._.getDataChannel, "text", {});
+ });
+
+ it("should not get the data channel if data channels are not wanted", function() {
+ driver._useDataChannels = false;
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.notCalled(fakeSubscriberObject._.getDataChannel);
+ });
+
+ it("should dispatch `DataChannelsAvailable` if the publisher channel is setup", function() {
+ // Fake a publisher channel.
+ driver._publisherChannel = {};
+
+ fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel);
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.called(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
+ new sharedActions.DataChannelsAvailable({
+ available: true
+ }));
+ });
+
+ it("should not dispatch `DataChannelsAvailable` if the publisher channel isn't setup", function() {
+ fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel);
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ sinon.assert.neverCalledWith(dispatcher.dispatch,
+ new sharedActions.DataChannelsAvailable({
+ available: true
+ }));
+ });
+
+ it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() {
+ var data = '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT +
+ '","message":"Are you there?","receivedTimestamp": "2015-06-25T00:29:14.197Z"}';
+ var clock = sinon.useFakeTimers();
+
+ fakeSubscriberObject._.getDataChannel.callsArgWith(2, null, fakeChannel);
+
+ session.trigger("streamCreated", { stream: fakeStream });
+
+ // Now send the message.
+ fakeChannel.trigger("message", {
+ data: data
+ });
+
+ sinon.assert.called(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
+ new sharedActions.ReceivedTextChatMessage({
+ contentType: CHAT_CONTENT_TYPES.TEXT,
+ message: "Are you there?",
+ receivedTimestamp: "1970-01-01T00:00:00.000Z"
+ }));
+
+ /* Restore the time. */
+ clock.restore();
+ });
});
});
- it("should not trigger readyForDataChannel signal if data channels are not wanted", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- document.createElement("video"));
- driver._useDataChannels = false;
- fakeStream.connection = fakeConnection;
+ describe("screen sharing streams", function() {
+ it("should subscribe to a screen sharing stream", function() {
+ fakeStream.videoType = "screen";
- session.trigger("streamCreated", { stream: fakeStream });
+ session.trigger("streamCreated", { stream: fakeStream });
- sinon.assert.notCalled(session.signal);
- });
+ sinon.assert.calledOnce(session.subscribe);
+ sinon.assert.calledWithExactly(session.subscribe,
+ fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig,
+ sinon.match.func);
+ });
- it("should subscribe to a screen sharing stream", function() {
- fakeStream.videoType = "screen";
-
- session.trigger("streamCreated", { stream: fakeStream });
-
- sinon.assert.calledOnce(session.subscribe);
- sinon.assert.calledWithExactly(session.subscribe,
- fakeStream, sinon.match.instanceOf(HTMLDivElement), publisherConfig,
- sinon.match.func);
- });
-
- it("should dispatch a mediaConnected action if both streams are up", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- videoElement);
- driver._publishedLocalStream = true;
-
- session.trigger("streamCreated", { stream: fakeStream });
-
- // Called twice due to the VideoDimensionsChanged above.
- sinon.assert.called(dispatcher.dispatch);
- sinon.assert.calledWithMatch(dispatcher.dispatch,
- new sharedActions.MediaConnected({}));
- });
-
- it("should store the start time when both streams are up and" +
- " driver._sendTwoWayMediaTelemetry is true", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- videoElement);
- driver._sendTwoWayMediaTelemetry = true;
- driver._publishedLocalStream = true;
- var startTime = 1;
- sandbox.stub(performance, "now").returns(startTime);
-
- session.trigger("streamCreated", { stream: fakeStream });
-
- expect(driver._getTwoWayMediaStartTime()).to.eql(startTime);
- });
-
- it("should not store the start time when both streams are up and" +
- " driver._isDesktop is false", function() {
- session.subscribe.yieldsOn(driver, null, fakeSubscriberObject,
- videoElement);
- driver._isDesktop = false;
- driver._publishedLocalStream = true;
- var startTime = 73;
- sandbox.stub(performance, "now").returns(startTime);
-
- session.trigger("streamCreated", { stream: fakeStream });
-
- expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime);
- });
-
-
- it("should not dispatch a mediaConnected action for screen sharing streams",
- function() {
+ it("should not dispatch a mediaConnected action for screen sharing streams", function() {
driver._publishedLocalStream = true;
fakeStream.videoType = "screen";
@@ -1130,16 +1198,14 @@ describe("loop.OTSdkDriver", function() {
sinon.match.hasOwn("name", "mediaConnected"));
});
- it("should not dispatch a ReceivingScreenShare action for camera streams",
- function() {
+ it("should not dispatch a ReceivingScreenShare action for camera streams", function() {
session.trigger("streamCreated", { stream: fakeStream });
sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({ receiving: true }));
});
- it("should dispatch a ReceivingScreenShare action for screen" +
- " sharing streams", function() {
+ it("should dispatch a ReceivingScreenShare action for screen sharing streams", function() {
fakeStream.videoType = "screen";
session.trigger("streamCreated", { stream: fakeStream });
@@ -1149,6 +1215,7 @@ describe("loop.OTSdkDriver", function() {
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({ receiving: true }));
});
+ });
});
describe("streamDestroyed: publisher/local", function() {
@@ -1499,16 +1566,11 @@ describe("loop.OTSdkDriver", function() {
sinon.assert.calledOnce(publisher._.getDataChannel);
});
- it("should get the data channel for the subscriber", function() {
- session.trigger("signal:readyForDataChannel");
-
- sinon.assert.calledOnce(subscriber._.getDataChannel);
- });
-
- it("should dispatch `DataChannelsAvailable` once both data channels have been obtained", function() {
+ it("should dispatch `DataChannelsAvailable` if the subscriber channel is setup", function() {
var fakeChannel = _.extend({}, Backbone.Events);
- subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel);
+ driver._subscriberChannel = fakeChannel;
+
publisher._.getDataChannel.callsArgWith(2, null, fakeChannel);
session.trigger("signal:readyForDataChannel");
@@ -1520,31 +1582,17 @@ describe("loop.OTSdkDriver", function() {
}));
});
- it("should dispatch `ReceivedTextChatMessage` when a text message is received", function() {
+ it("should not dispatch `DataChannelsAvailable` if the subscriber channel isn't setup", function() {
var fakeChannel = _.extend({}, Backbone.Events);
- var data = '{"contentType":"' + CHAT_CONTENT_TYPES.TEXT +
- '","message":"Are you there?","receivedTimestamp": "2015-06-25T00:29:14.197Z"}';
- var clock = sinon.useFakeTimers();
- subscriber._.getDataChannel.callsArgWith(2, null, fakeChannel);
+ publisher._.getDataChannel.callsArgWith(2, null, fakeChannel);
session.trigger("signal:readyForDataChannel");
- // Now send the message.
- fakeChannel.trigger("message", {
- data: data
- });
-
- sinon.assert.calledOnce(dispatcher.dispatch);
- sinon.assert.calledWithExactly(dispatcher.dispatch,
- new sharedActions.ReceivedTextChatMessage({
- contentType: CHAT_CONTENT_TYPES.TEXT,
- message: "Are you there?",
- receivedTimestamp: "1970-01-01T00:00:00.000Z"
+ sinon.assert.neverCalledWith(dispatcher.dispatch,
+ new sharedActions.DataChannelsAvailable({
+ available: true
}));
-
- /* Restore the time. */
- clock.restore();
});
});
From 3ecdeaefe5c28a1f8b812e0296b0083ec399875d Mon Sep 17 00:00:00 2001
From: Sami Jaktholm
Date: Sat, 7 Nov 2015 08:28:38 +0200
Subject: [PATCH 04/71] Bug 1199180 - Revert rev f508744adc9f as it did not
help with the intermittent failures. r=pbrosset
---
devtools/client/markupview/test/browser.ini | 1 -
.../test/browser_markupview_keybindings_04.js | 39 +++-------------
.../markupview/test/frame-script-utils.js | 46 -------------------
devtools/client/markupview/test/head.js | 2 -
4 files changed, 7 insertions(+), 81 deletions(-)
delete mode 100644 devtools/client/markupview/test/frame-script-utils.js
diff --git a/devtools/client/markupview/test/browser.ini b/devtools/client/markupview/test/browser.ini
index 4bde459bf21..169e90ea506 100644
--- a/devtools/client/markupview/test/browser.ini
+++ b/devtools/client/markupview/test/browser.ini
@@ -23,7 +23,6 @@ support-files =
doc_markup_toggle.html
doc_markup_tooltip.png
doc_markup_xul.xul
- frame-script-utils.js
head.js
helper_attributes_test_runner.js
helper_events_test_runner.js
diff --git a/devtools/client/markupview/test/browser_markupview_keybindings_04.js b/devtools/client/markupview/test/browser_markupview_keybindings_04.js
index 3bf81cbe876..c36c271e4b1 100644
--- a/devtools/client/markupview/test/browser_markupview_keybindings_04.js
+++ b/devtools/client/markupview/test/browser_markupview_keybindings_04.js
@@ -45,28 +45,10 @@ function assertNodeSelected(inspector, tagName) {
}
function* selectWithBrowserMenu(inspector) {
- // This test can't use BrowserTestUtils.synthesizeMouseAtCenter()
- // method (see below) since it causes intermittent test failures.
- // So, we are introducing a new "Test:MarkupView:SynthesizeMouse" event
- // that is handled in the content scope. The main difference between
- // this new event and BrowserTestUtils library is EventUtils library.
- // While BrowserTestUtils is using:
- // chrome://mochikit/content/tests/SimpleTest/EventUtils.js
- // (see: AsyncUtilsContent.js)
- // ... this test requires:
- // chrome://marionette/content/EventUtils.js
- // (see markupview/test/frame-script-utils.js)
- // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=1199180
- yield executeInContent("Test:MarkupView:SynthesizeMouse", {
- center: true,
- selector: "div",
- options: {type: "contextmenu", button: 2}
- });
-
- //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
- // type: "contextmenu",
- // button: 2
- //}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+ type: "contextmenu",
+ button: 2
+ }, gBrowser.selectedBrowser);
// nsContextMenu also requires the popupNode to be set, but we can't set it to
// node under e10s as it's a CPOW, not a DOM node. But under e10s,
@@ -89,16 +71,9 @@ function* selectWithBrowserMenu(inspector) {
function* selectWithElementPicker(inspector) {
yield inspector.toolbox.highlighterUtils.startPicker();
- yield executeInContent("Test:MarkupView:SynthesizeMouse", {
- center: true,
- selector: "div",
- options: {type: "mousemove"}
- });
-
- // Read comment in selectWithBrowserMenu() method.
- //yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
- // type: "mousemove",
- //}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
+ type: "mousemove",
+ }, gBrowser.selectedBrowser);
executeInContent("Test:SynthesizeKey", {
key: "VK_RETURN",
diff --git a/devtools/client/markupview/test/frame-script-utils.js b/devtools/client/markupview/test/frame-script-utils.js
deleted file mode 100644
index b004e38d4d9..00000000000
--- a/devtools/client/markupview/test/frame-script-utils.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var {classes: Cc, interfaces: Ci} = Components;
-const subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Ci.mozIJSSubScriptLoader);
-var EventUtils = {};
-subScriptLoader.loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
-
-/**
- * Synthesize a mouse event on an element. This handler doesn't send a message
- * back. Consumers should listen to specific events on the inspector/highlighter
- * to know when the event got synthesized.
- * @param {Object} msg The msg.data part expects the following properties:
- * - {Number} x
- * - {Number} y
- * - {Boolean} center If set to true, x/y will be ignored and
- * synthesizeMouseAtCenter will be used instead
- * - {Object} options Other event options
- * - {String} selector An optional selector that will be used to find the node to
- * synthesize the event on, if msg.objects doesn't contain the CPOW.
- * The msg.objects part should be the element.
- * @param {Object} data Event detail properties:
- */
-addMessageListener("Test:MarkupView:SynthesizeMouse", function(msg) {
- let {x, y, center, options, selector} = msg.data;
- let {node} = msg.objects;
-
- if (!node && selector) {
- node = content.document.querySelector(selector);
- }
-
- if (center) {
- EventUtils.synthesizeMouseAtCenter(node, options, node.ownerDocument.defaultView);
- } else {
- EventUtils.synthesizeMouse(node, x, y, options, node.ownerDocument.defaultView);
- }
-
- // Most consumers won't need to listen to this message, unless they want to
- // wait for the mouse event to be synthesized and don't have another event
- // to listen to instead.
- sendAsyncMessage("Test:MarkupView:SynthesizeMouse");
-});
diff --git a/devtools/client/markupview/test/head.js b/devtools/client/markupview/test/head.js
index d01f95f6e8e..ffe09abad97 100644
--- a/devtools/client/markupview/test/head.js
+++ b/devtools/client/markupview/test/head.js
@@ -51,7 +51,6 @@ registerCleanupFunction(function*() {
const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/markupview/test/";
const CHROME_BASE = "chrome://mochitests/content/browser/devtools/client/markupview/test/";
const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js";
-const MARKUPVIEW_FRAME_SCRIPT_URL = CHROME_BASE + "frame-script-utils.js";
/**
* Add a new test tab in the browser and load the given url.
@@ -72,7 +71,6 @@ function addTab(url) {
info("Loading the helper frame script " + COMMON_FRAME_SCRIPT_URL);
linkedBrowser.messageManager.loadFrameScript(COMMON_FRAME_SCRIPT_URL, false);
- linkedBrowser.messageManager.loadFrameScript(MARKUPVIEW_FRAME_SCRIPT_URL, false);
linkedBrowser.addEventListener("load", function onload() {
linkedBrowser.removeEventListener("load", onload, true);
From 21041eaa5a4d6571f03eefa1126c2ffc0385f1b8 Mon Sep 17 00:00:00 2001
From: Sami Jaktholm
Date: Sat, 7 Nov 2015 08:27:16 +0200
Subject: [PATCH 05/71] Bug 1199180 - Wait for the inspector-updated event
after selecting nodes with UP key. r=pbrosset
The likely steps that lead to intermittent failures in
browser_markupview_keybindings_04.js are:
1) UP key is pressed, the test waits for child updates and
node-highlight event.
2) Once those have finished, the selection has changed to BUT
the inspector-updated event for the change has not been emitted
(child node update finishes before the event is emitted).
3) The test calls selectWithElementPicker() presses ENTER to pick the
element and starts to wait for an inspector-updated event.
4) The inspector-updated event from (1) is finally emitted and the test
continues BUT the selection change at (3) has not yet completed.
This means is still selected and the assertion fails.
Since a new selection will always cause the inspector-updated event to
be emitted it makes sense to wait for it after pressing UP.
---
.../test/browser_markupview_keybindings_04.js | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/devtools/client/markupview/test/browser_markupview_keybindings_04.js b/devtools/client/markupview/test/browser_markupview_keybindings_04.js
index c36c271e4b1..2529641b3e8 100644
--- a/devtools/client/markupview/test/browser_markupview_keybindings_04.js
+++ b/devtools/client/markupview/test/browser_markupview_keybindings_04.js
@@ -20,10 +20,7 @@ add_task(function*() {
info("Press arrowUp to focus " +
"(which works if the node was focused properly)");
- let onNodeHighlighted = toolbox.once("node-highlight");
- EventUtils.synthesizeKey("VK_UP", {});
- yield waitForChildrenUpdated(inspector);
- yield onNodeHighlighted;
+ yield selectPreviousNodeWithArrowUp(inspector);
assertNodeSelected(inspector, "body");
info("Select the test node with the element picker");
@@ -32,10 +29,7 @@ add_task(function*() {
info("Press arrowUp to focus " +
"(which works if the node was focused properly)");
- onNodeHighlighted = toolbox.once("node-highlight");
- EventUtils.synthesizeKey("VK_UP", {});
- yield waitForChildrenUpdated(inspector);
- yield onNodeHighlighted;
+ yield selectPreviousNodeWithArrowUp(inspector);
assertNodeSelected(inspector, "body");
});
@@ -44,6 +38,13 @@ function assertNodeSelected(inspector, tagName) {
`The <${tagName}> node is selected`);
}
+function selectPreviousNodeWithArrowUp(inspector) {
+ let onNodeHighlighted = inspector.toolbox.once("node-highlight");
+ let onUpdated = inspector.once("inspector-updated");
+ EventUtils.synthesizeKey("VK_UP", {});
+ return Promise.all([onUpdated, onNodeHighlighted]);
+}
+
function* selectWithBrowserMenu(inspector) {
yield BrowserTestUtils.synthesizeMouseAtCenter("div", {
type: "contextmenu",
From 2ec37425331bf6c1073fe5c2e24ec3b963bde221 Mon Sep 17 00:00:00 2001
From: Jared Wein
Date: Mon, 9 Nov 2015 18:15:38 -0500
Subject: [PATCH 06/71] Bug 1205436 - Add extra logging to PanelUI show/hide
and customizing enter/exit. r=Gijs
---
browser/components/customizableui/CustomizeMode.jsm | 6 ++++--
browser/components/customizableui/content/panelUI.js | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
index f9d0b9f94e0..b585b6e047a 100644
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -309,10 +309,12 @@ CustomizeMode.prototype = {
this.exit();
}
}.bind(this)).then(null, function(e) {
- ERROR(e);
+ ERROR("Error entering customize mode", e);
// We should ensure this has been called, and calling it again doesn't hurt:
window.PanelUI.endBatchUpdate();
this._handler.isEnteringCustomizeMode = false;
+ // Exit customize mode to ensure proper clean-up when entering failed.
+ this.exit();
}.bind(this));
},
@@ -482,7 +484,7 @@ CustomizeMode.prototype = {
this.enter();
}
}.bind(this)).then(null, function(e) {
- ERROR(e);
+ ERROR("Error exiting customize mode", e);
// We should ensure this has been called, and calling it again doesn't hurt:
window.PanelUI.endBatchUpdate();
this._handler.isExitingCustomizeMode = false;
diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js
index b03a44aca7a..bc31707257a 100644
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -166,6 +166,8 @@ const PanelUI = {
document.getAnonymousElementByAttribute(anchor, "class",
"toolbarbutton-icon");
this.panel.openPopup(iconAnchor || anchor);
+ }, (reason) => {
+ console.error("Error showing the PanelUI menu", reason);
});
return deferred.promise;
From d79c059e05f3db91e4d0935f55c778f9b25e04cd Mon Sep 17 00:00:00 2001
From: Tom Tromey
Date: Mon, 9 Nov 2015 07:01:00 +0100
Subject: [PATCH 07/71] Bug 1216234 - add inIDOMUtils.getCSSPseudoElementNames;
r=heycam,pbrosset
---
devtools/client/styleinspector/rule-view.js | 7 ++-
.../browser_ruleview_user-agent-styles.js | 8 +++
devtools/server/actors/styles.js | 21 ++-----
.../tests/mochitest/test_styles-applied.html | 2 +-
devtools/shared/styleinspector/css-logic.js | 42 --------------
layout/inspector/inDOMUtils.cpp | 23 ++++++++
layout/inspector/inIDOMUtils.idl | 13 ++++-
layout/inspector/tests/mochitest.ini | 1 +
.../tests/test_getCSSPseudoElementNames.html | 57 +++++++++++++++++++
9 files changed, 111 insertions(+), 63 deletions(-)
create mode 100644 layout/inspector/tests/test_getCSSPseudoElementNames.html
diff --git a/devtools/client/styleinspector/rule-view.js b/devtools/client/styleinspector/rule-view.js
index 88948d2fe2b..b89e4dbce22 100644
--- a/devtools/client/styleinspector/rule-view.js
+++ b/devtools/client/styleinspector/rule-view.js
@@ -13,8 +13,7 @@ const {setTimeout, clearTimeout} =
const {CssLogic} = require("devtools/shared/styleinspector/css-logic");
const {InplaceEditor, editableField, editableItem} =
require("devtools/client/shared/inplace-editor");
-const {ELEMENT_STYLE, PSEUDO_ELEMENTS} =
- require("devtools/server/actors/styles");
+const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
const {OutputParser} = require("devtools/shared/output-parser");
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
const {
@@ -4059,3 +4058,7 @@ XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
loader.lazyGetter(this, "AutocompletePopup", function() {
return require("devtools/client/shared/autocomplete-popup").AutocompletePopup;
});
+
+loader.lazyGetter(this, "PSEUDO_ELEMENTS", () => {
+ return domUtils.getCSSPseudoElementNames();
+});
diff --git a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js
index da3fef8ddd0..43f1f329aa4 100644
--- a/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js
+++ b/devtools/client/styleinspector/test/browser_ruleview_user-agent-styles.js
@@ -160,6 +160,14 @@ function* compareAppliedStylesWithUI(inspector, view, filter) {
filter: filter
});
+ // We may see multiple entries that map to a given rule; filter the
+ // duplicates here to match what the UI does.
+ let entryMap = new Map();
+ for (let entry of entries) {
+ entryMap.set(entry.rule, entry);
+ }
+ entries = [...entryMap.values()];
+
let elementStyle = view._elementStyle;
is(elementStyle.rules.length, entries.length,
"Should have correct number of rules (" + entries.length + ")");
diff --git a/devtools/server/actors/styles.js b/devtools/server/actors/styles.js
index f5190a9546a..88b264db444 100644
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -12,7 +12,6 @@ const {Arg, Option, method, RetVal, types} = protocol;
const events = require("sdk/event/core");
const {Class} = require("sdk/core/heritage");
const {LongStringActor} = require("devtools/server/actors/string");
-const {PSEUDO_ELEMENT_SET} = require("devtools/shared/styleinspector/css-logic");
// This will also add the "stylesheet" actor type for protocol.js to recognize
const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} =
@@ -35,24 +34,12 @@ loader.lazyGetter(this, "RuleRewriter", () => {
const ELEMENT_STYLE = 100;
exports.ELEMENT_STYLE = ELEMENT_STYLE;
-// Not included since these are uneditable by the user.
-// See https://hg.mozilla.org/mozilla-central/file/696a4ad5d011/layout/style/nsCSSPseudoElementList.h#l74
-PSEUDO_ELEMENT_SET.delete(":-moz-meter-bar");
-PSEUDO_ELEMENT_SET.delete(":-moz-list-bullet");
-PSEUDO_ELEMENT_SET.delete(":-moz-list-number");
-PSEUDO_ELEMENT_SET.delete(":-moz-focus-inner");
-PSEUDO_ELEMENT_SET.delete(":-moz-focus-outer");
-PSEUDO_ELEMENT_SET.delete(":-moz-math-anonymous");
-PSEUDO_ELEMENT_SET.delete(":-moz-math-stretchy");
-
-const PSEUDO_ELEMENTS = Array.from(PSEUDO_ELEMENT_SET);
-
-exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS;
-
// When gathering rules to read for pseudo elements, we will skip
// :before and :after, which are handled as a special case.
-const PSEUDO_ELEMENTS_TO_READ = PSEUDO_ELEMENTS.filter(pseudo => {
- return pseudo !== ":before" && pseudo !== ":after";
+loader.lazyGetter(this, "PSEUDO_ELEMENTS_TO_READ", () => {
+ return DOMUtils.getCSSPseudoElementNames().filter(pseudo => {
+ return pseudo !== ":before" && pseudo !== ":after";
+ });
});
const XHTML_NS = "http://www.w3.org/1999/xhtml";
diff --git a/devtools/server/tests/mochitest/test_styles-applied.html b/devtools/server/tests/mochitest/test_styles-applied.html
index fc9f04f6ee6..5afa1c30995 100644
--- a/devtools/server/tests/mochitest/test_styles-applied.html
+++ b/devtools/server/tests/mochitest/test_styles-applied.html
@@ -85,7 +85,7 @@ addTest(function inheritedSystemStyles() {
ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style");
is(applied[1].rule.type, 1, "Entry 1 should be a rule style");
- is(applied.length, 8, "Should have 8 rules.");
+ is(applied.length, 11, "Should have 11 rules.");
}).then(runNextTest));
});
diff --git a/devtools/shared/styleinspector/css-logic.js b/devtools/shared/styleinspector/css-logic.js
index 80f1feec647..c0b28d01f02 100644
--- a/devtools/shared/styleinspector/css-logic.js
+++ b/devtools/shared/styleinspector/css-logic.js
@@ -43,36 +43,6 @@ const Services = require("Services");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { getRootBindingParent } = require("devtools/shared/layout/utils");
-var pseudos = new Set([
- ":after",
- ":before",
- ":first-letter",
- ":first-line",
- ":selection",
- ":-moz-color-swatch",
- ":-moz-focus-inner",
- ":-moz-focus-outer",
- ":-moz-list-bullet",
- ":-moz-list-number",
- ":-moz-math-anonymous",
- ":-moz-math-stretchy",
- ":-moz-meter-bar",
- ":-moz-number-spin-box",
- ":-moz-number-spin-down",
- ":-moz-number-spin-up",
- ":-moz-number-text",
- ":-moz-number-wrapper",
- ":-moz-placeholder",
- ":-moz-progress-bar",
- ":-moz-range-progress",
- ":-moz-range-thumb",
- ":-moz-range-track",
- ":-moz-selection"
-]);
-
-const PSEUDO_ELEMENT_SET = pseudos;
-exports.PSEUDO_ELEMENT_SET = PSEUDO_ELEMENT_SET;
-
// This should be ok because none of the functions that use this should be used
// on the worker thread, where Cu is not available.
if (Cu) {
@@ -1678,18 +1648,6 @@ CssSelector.prototype = {
return this.cssRule.line;
},
- /**
- * Retrieve the pseudo-elements that we support. This list should match the
- * elements specified in layout/style/nsCSSPseudoElementList.h
- */
- get pseudoElements()
- {
- if (!CssSelector._pseudoElements) {
- CssSelector._pseudoElements = PSEUDO_ELEMENT_SET;
- }
- return CssSelector._pseudoElements;
- },
-
/**
* Retrieve specificity information for the current selector.
*
diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp
index dccd7ccef02..f176168806b 100644
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -1191,6 +1191,29 @@ GetStatesForPseudoClass(const nsAString& aStatePseudo)
return sPseudoClassStates[nsCSSPseudoClasses::GetPseudoType(atom)];
}
+NS_IMETHODIMP
+inDOMUtils::GetCSSPseudoElementNames(uint32_t* aLength, char16_t*** aNames)
+{
+ nsTArray array;
+
+ for (int i = 0; i < nsCSSPseudoElements::ePseudo_PseudoElementCount; ++i) {
+ nsCSSPseudoElements::Type type = static_cast(i);
+ if (!nsCSSPseudoElements::PseudoElementIsUASheetOnly(type)) {
+ nsIAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type);
+ array.AppendElement(atom);
+ }
+ }
+
+ *aLength = array.Length();
+ char16_t** ret =
+ static_cast(moz_xmalloc(*aLength * sizeof(char16_t*)));
+ for (uint32_t i = 0; i < *aLength; ++i) {
+ ret[i] = ToNewUnicode(nsDependentAtomString(array[i]));
+ }
+ *aNames = ret;
+ return NS_OK;
+}
+
NS_IMETHODIMP
inDOMUtils::AddPseudoClassLock(nsIDOMElement *aElement,
const nsAString &aPseudoClass)
diff --git a/layout/inspector/inIDOMUtils.idl b/layout/inspector/inIDOMUtils.idl
index daaff3a98eb..ddc79710ade 100644
--- a/layout/inspector/inIDOMUtils.idl
+++ b/layout/inspector/inIDOMUtils.idl
@@ -17,7 +17,7 @@ interface nsIDOMFontFaceList;
interface nsIDOMRange;
interface nsIDOMCSSStyleSheet;
-[scriptable, uuid(d67c0463-592e-4d7c-b67e-923ee3f6c643)]
+[scriptable, uuid(ec3dc3d5-41d1-4d08-ace5-7e944de6614d)]
interface inIDOMUtils : nsISupports
{
// CSS utilities
@@ -162,6 +162,17 @@ interface inIDOMUtils : nsISupports
nsIDOMFontFaceList getUsedFontFaces(in nsIDOMRange aRange);
+ /**
+ * Get the names of all the supported pseudo-elements.
+ * Pseudo-elements which are only accepted in UA style sheets are
+ * not included.
+ *
+ * @param {unsigned long} aCount the number of items returned
+ * @param {wstring[]} aNames the names
+ */
+ void getCSSPseudoElementNames([optional] out unsigned long aCount,
+ [retval, array, size_is(aCount)] out wstring aNames);
+
// pseudo-class style locking methods. aPseudoClass must be a valid pseudo-class
// selector string, e.g. ":hover". ":-moz-any-link" and non-event-state
// pseudo-classes are ignored.
diff --git a/layout/inspector/tests/mochitest.ini b/layout/inspector/tests/mochitest.ini
index 4dd9acdd3a9..25209f57a67 100644
--- a/layout/inspector/tests/mochitest.ini
+++ b/layout/inspector/tests/mochitest.ini
@@ -19,6 +19,7 @@ support-files =
[test_color_to_rgba.html]
[test_css_property_is_shorthand.html]
[test_css_property_is_valid.html]
+[test_getCSSPseudoElementNames.html]
[test_getRelativeRuleLine.html]
[test_get_all_style_sheets.html]
[test_is_valid_css_color.html]
diff --git a/layout/inspector/tests/test_getCSSPseudoElementNames.html b/layout/inspector/tests/test_getCSSPseudoElementNames.html
new file mode 100644
index 00000000000..6085a018c9a
--- /dev/null
+++ b/layout/inspector/tests/test_getCSSPseudoElementNames.html
@@ -0,0 +1,57 @@
+
+
+
+
+ Test inDOMUtils::getCSSPseudoElementNames
+
+
+
+
+
+
Test inDOMUtils::getCSSPseudoElementNames
+
+
+
+
+
+
+
+
From a236a44a79de48576957af6520b687afe9c3fe88 Mon Sep 17 00:00:00 2001
From: Jordan Santell
Date: Fri, 6 Nov 2015 20:40:00 +0100
Subject: [PATCH 08/71] Bug 1216542 - disable
browser_wa_properties-view-media-nodes.js on OSX, due to failures on 10.10.5.
r=vp
---
devtools/client/webaudioeditor/test/browser.ini | 1 +
1 file changed, 1 insertion(+)
diff --git a/devtools/client/webaudioeditor/test/browser.ini b/devtools/client/webaudioeditor/test/browser.ini
index 073f378b47d..cad17a5305a 100644
--- a/devtools/client/webaudioeditor/test/browser.ini
+++ b/devtools/client/webaudioeditor/test/browser.ini
@@ -68,6 +68,7 @@ skip-if = true # bug 1010423
[browser_wa_properties-view-edit-02.js]
skip-if = true # bug 1010423
[browser_wa_properties-view-media-nodes.js]
+skip-if = os == 'mac' # bug 1216542
[browser_wa_properties-view-params.js]
[browser_wa_properties-view-params-objects.js]
[browser_wa_reset-01.js]
From bd123b8860650bc22b453abd502d9ffdb97de4c2 Mon Sep 17 00:00:00 2001
From: Tom Tromey
Date: Fri, 6 Nov 2015 10:43:00 +0100
Subject: [PATCH 09/71] Bug 1222461 - fix how rewriteDeclarations reports
unrelated changes. r=pbrosset
---
devtools/client/shared/css-parsing-utils.js | 36 ++++++++++----
.../test/unit/test_rewriteDeclarations.js | 48 +++++++++++++------
2 files changed, 60 insertions(+), 24 deletions(-)
diff --git a/devtools/client/shared/css-parsing-utils.js b/devtools/client/shared/css-parsing-utils.js
index 6b44a7c06b7..d4782b934be 100644
--- a/devtools/client/shared/css-parsing-utils.js
+++ b/devtools/client/shared/css-parsing-utils.js
@@ -567,7 +567,11 @@ RuleRewriter.prototype = {
* declarations.
*
* @param {String} text The input text. This should include the trailing ";".
- * @return {String} Text that has been rewritten to be "lexically safe".
+ * @return {Array} An array of the form [anySanitized, text], where
+ * |anySanitized| is a boolean that indicates
+ * whether anything substantive has changed; and
+ * where |text| is the text that has been rewritten
+ * to be "lexically safe".
*/
sanitizePropertyValue: function(text) {
let lexer = DOMUtils.getCSSLexer(text);
@@ -575,6 +579,7 @@ RuleRewriter.prototype = {
let result = "";
let previousOffset = 0;
let braceDepth = 0;
+ let anySanitized = false;
while (true) {
let token = lexer.nextToken();
if (!token) {
@@ -605,6 +610,7 @@ RuleRewriter.prototype = {
// Quote the offending symbol.
result += "\\" + token.text;
previousOffset = token.endOffset;
+ anySanitized = true;
}
break;
}
@@ -612,9 +618,13 @@ RuleRewriter.prototype = {
}
// Copy out any remaining text, then any needed terminators.
- result += text.substring(previousOffset, text.length) +
- lexer.performEOFFixup("", true);
- return result;
+ result += text.substring(previousOffset, text.length);
+ let eofFixup = lexer.performEOFFixup("", true);
+ if (eofFixup) {
+ anySanitized = true;
+ result += eofFixup;
+ }
+ return [anySanitized, result];
},
/**
@@ -662,10 +672,16 @@ RuleRewriter.prototype = {
// before any trailing whitespace.
this.result = this.result.substring(0, endIndex) + termDecl.terminator +
trailingText;
- // The terminator includes the ";", but we don't want it in
- // the changed value.
- this.changedDeclarations[index] =
- termDecl.value + termDecl.terminator.slice(0, -1);
+ // In a couple of cases, we may have had to add something to
+ // terminate the declaration, but the termination did not
+ // actually affect the property's value -- and at this spot, we
+ // only care about reporting value changes. In particular, we
+ // might have added a plain ";", or we might have terminated a
+ // comment with "*/;". Neither of these affect the value.
+ if (termDecl.terminator !== ";" && termDecl.terminator !== "*/;") {
+ this.changedDeclarations[index] =
+ termDecl.value + termDecl.terminator.slice(0, -1);
+ }
}
// If the rule generally has newlines, but this particular
// declaration doesn't have a trailing newline, insert one now.
@@ -685,8 +701,8 @@ RuleRewriter.prototype = {
* @return {String} The sanitized text.
*/
sanitizeText: function(text, index) {
- let sanitizedText = this.sanitizePropertyValue(text);
- if (sanitizedText !== text) {
+ let [anySanitized, sanitizedText] = this.sanitizePropertyValue(text);
+ if (anySanitized) {
this.changedDeclarations[index] = sanitizedText;
}
return sanitizedText;
diff --git a/devtools/client/shared/test/unit/test_rewriteDeclarations.js b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
index 1668ba03bfc..936b38d0ecd 100644
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -269,7 +269,8 @@ const TEST_DATA = [
desc: "enable single quote termination",
input: "/* content: 'hi */ color: red;",
instruction: {type: "enable", name: "content", value: true, index: 0},
- expected: "content: 'hi'; color: red;"
+ expected: "content: 'hi'; color: red;",
+ changed: {0: "'hi'"}
},
// Termination insertion corner case.
{
@@ -277,7 +278,8 @@ const TEST_DATA = [
input: "content: 'hi",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "content: 'hi';color: red;"
+ expected: "content: 'hi';color: red;",
+ changed: {0: "'hi'"}
},
// Termination insertion corner case.
@@ -285,7 +287,8 @@ const TEST_DATA = [
desc: "enable double quote termination",
input: "/* content: \"hi */ color: red;",
instruction: {type: "enable", name: "content", value: true, index: 0},
- expected: "content: \"hi\"; color: red;"
+ expected: "content: \"hi\"; color: red;",
+ changed: {0: "\"hi\""}
},
// Termination insertion corner case.
{
@@ -293,7 +296,8 @@ const TEST_DATA = [
input: "content: \"hi",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "content: \"hi\";color: red;"
+ expected: "content: \"hi\";color: red;",
+ changed: {0: "\"hi\""}
},
// Termination insertion corner case.
@@ -302,7 +306,8 @@ const TEST_DATA = [
input: "/* background-image: url(something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
- expected: "background-image: url(something.jpg); color: red;"
+ expected: "background-image: url(something.jpg); color: red;",
+ changed: {0: "url(something.jpg)"}
},
// Termination insertion corner case.
{
@@ -310,7 +315,8 @@ const TEST_DATA = [
input: "background-image: url(something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "background-image: url(something.jpg);color: red;"
+ expected: "background-image: url(something.jpg);color: red;",
+ changed: {0: "url(something.jpg)"}
},
// Termination insertion corner case.
@@ -319,7 +325,8 @@ const TEST_DATA = [
input: "/* background-image: url('something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
- expected: "background-image: url('something.jpg'); color: red;"
+ expected: "background-image: url('something.jpg'); color: red;",
+ changed: {0: "url('something.jpg')"}
},
// Termination insertion corner case.
{
@@ -327,7 +334,8 @@ const TEST_DATA = [
input: "background-image: url('something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "background-image: url('something.jpg');color: red;"
+ expected: "background-image: url('something.jpg');color: red;",
+ changed: {0: "url('something.jpg')"}
},
// Termination insertion corner case.
@@ -336,7 +344,8 @@ const TEST_DATA = [
input: "/* background-image: url(\"something.jpg */ color: red;",
instruction: {type: "enable", name: "background-image", value: true,
index: 0},
- expected: "background-image: url(\"something.jpg\"); color: red;"
+ expected: "background-image: url(\"something.jpg\"); color: red;",
+ changed: {0: "url(\"something.jpg\")"}
},
// Termination insertion corner case.
{
@@ -344,7 +353,8 @@ const TEST_DATA = [
input: "background-image: url(\"something.jpg",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "background-image: url(\"something.jpg\");color: red;"
+ expected: "background-image: url(\"something.jpg\");color: red;",
+ changed: {0: "url(\"something.jpg\")"}
},
// Termination insertion corner case.
@@ -353,7 +363,10 @@ const TEST_DATA = [
input: "something: \\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "something: \\\\;color: red;"
+ expected: "something: \\\\;color: red;",
+ // The lexer rewrites the token before we see it. However this is
+ // so obscure as to be inconsequential.
+ changed: {0: "\uFFFD\\"}
},
// Termination insertion corner case.
@@ -362,14 +375,16 @@ const TEST_DATA = [
input: "something: '\\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "something: '\\\\';color: red;"
+ expected: "something: '\\\\';color: red;",
+ changed: {0: "'\\\\'"}
},
{
desc: "enable backslash double quote termination",
input: "something: \"\\",
instruction: {type: "create", name: "color", value: "red", priority: "",
index: 1},
- expected: "something: \"\\\\\";color: red;"
+ expected: "something: \"\\\\\";color: red;",
+ changed: {0: "\"\\\\\""}
},
// Termination insertion corner case.
@@ -471,8 +486,13 @@ function run_test() {
let {changed, text} = rewriteDeclarations(test.input, test.instruction,
"\t");
equal(text, test.expected, "output for " + test.desc);
+
+ let expectChanged;
if ("changed" in test) {
- deepEqual(changed, test.changed, "changed result for " + test.desc);
+ expectChanged = test.changed;
+ } else {
+ expectChanged = {};
}
+ deepEqual(changed, expectChanged, "changed result for " + test.desc);
}
}
From d91bdfe63620bd3e1ee091bea06403f20d7cc51b Mon Sep 17 00:00:00 2001
From: rthyberg
Date: Sun, 8 Nov 2015 16:25:00 +0100
Subject: [PATCH 10/71] Bug 1164039 - moved TelemetryTimestamps.jsm and tests
to toolkit/componets/telemetry/. r=dexter
---
.../{modules => components/telemetry}/TelemetryTimestamps.jsm | 0
toolkit/components/telemetry/moz.build | 1 +
.../telemetry/tests/unit}/test_TelemetryTimestamps.js | 2 +-
toolkit/components/telemetry/tests/unit/xpcshell.ini | 2 ++
toolkit/modules/moz.build | 1 -
toolkit/modules/tests/xpcshell/xpcshell.ini | 2 --
6 files changed, 4 insertions(+), 4 deletions(-)
rename toolkit/{modules => components/telemetry}/TelemetryTimestamps.jsm (100%)
rename toolkit/{modules/tests/xpcshell => components/telemetry/tests/unit}/test_TelemetryTimestamps.js (98%)
diff --git a/toolkit/modules/TelemetryTimestamps.jsm b/toolkit/components/telemetry/TelemetryTimestamps.jsm
similarity index 100%
rename from toolkit/modules/TelemetryTimestamps.jsm
rename to toolkit/components/telemetry/TelemetryTimestamps.jsm
diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build
index b3ff140170f..ace0a44bbe4 100644
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -40,6 +40,7 @@ EXTRA_JS_MODULES += [
'TelemetrySession.jsm',
'TelemetryStopwatch.jsm',
'TelemetryStorage.jsm',
+ 'TelemetryTimestamps.jsm',
'TelemetryUtils.jsm',
'ThirdPartyCookieProbe.jsm',
'UITelemetry.jsm',
diff --git a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js b/toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js
similarity index 98%
rename from toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js
rename to toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js
index 915ce188e56..f8630579dd5 100644
--- a/toolkit/modules/tests/xpcshell/test_TelemetryTimestamps.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryTimestamps.js
@@ -24,7 +24,7 @@ var gGlobalScope = this;
function loadAddonManager() {
let ns = {};
Cu.import("resource://gre/modules/Services.jsm", ns);
- let head = "../../../mozapps/extensions/test/xpcshell/head_addons.js";
+ let head = "../../../../mozapps/extensions/test/xpcshell/head_addons.js";
let file = do_get_file(head);
let uri = ns.Services.io.newFileURI(file);
ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini
index a0965a3f995..5ec31ced526 100644
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -63,3 +63,5 @@ skip-if = os == "android"
tags = addons
[test_TelemetryReportingPolicy.js]
tags = addons
+[test_TelemetryTimestamps.js]
+skip-if = toolkit == 'android'
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index 5e6b75228b0..691eb3e8633 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -73,7 +73,6 @@ EXTRA_JS_MODULES += [
'SpatialNavigation.jsm',
'Sqlite.jsm',
'Task.jsm',
- 'TelemetryTimestamps.jsm',
'Timer.jsm',
'Troubleshoot.jsm',
'UpdateUtils.jsm',
diff --git a/toolkit/modules/tests/xpcshell/xpcshell.ini b/toolkit/modules/tests/xpcshell/xpcshell.ini
index 39e94d98bd5..f4bf5d872ce 100644
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -53,8 +53,6 @@ skip-if = toolkit == 'android'
skip-if = toolkit == 'android'
[test_task.js]
skip-if = toolkit == 'android'
-[test_TelemetryTimestamps.js]
-skip-if = toolkit == 'android'
[test_timer.js]
skip-if = toolkit == 'android'
[test_UpdateUtils_url.js]
From d1a634bff74c271a1c3ffbf2b6a85a859ccbcc00 Mon Sep 17 00:00:00 2001
From: simplyblue
Date: Fri, 6 Nov 2015 19:27:53 +0530
Subject: [PATCH 11/71] Bug 1175519 - Expose raw ping json data on
about:telemetry. r=gfritzsche
---
toolkit/content/aboutTelemetry.css | 22 +++++++++++++++++++
toolkit/content/aboutTelemetry.js | 15 ++++++++++++-
toolkit/content/aboutTelemetry.xhtml | 6 +++++
.../en-US/chrome/global/aboutTelemetry.dtd | 4 ++++
4 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/toolkit/content/aboutTelemetry.css b/toolkit/content/aboutTelemetry.css
index 43876480970..30797ad5e10 100644
--- a/toolkit/content/aboutTelemetry.css
+++ b/toolkit/content/aboutTelemetry.css
@@ -227,3 +227,25 @@ body[dir="rtl"] .copy-node {
.filter-blocked {
display: none;
}
+
+#raw-ping-data-section {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0px;
+ left: 0px;
+ background-color:-moz-Dialog;
+}
+
+#raw-ping-data {
+ background-color:white;
+ margin: 0px;
+}
+
+#hide-raw-ping {
+ float: right;
+ cursor: pointer;
+ font-size: 20px;
+ background-color:#d8d8d8;
+ padding: 5px 10px;
+}
diff --git a/toolkit/content/aboutTelemetry.js b/toolkit/content/aboutTelemetry.js
index fea5d79d804..dc5878c5b97 100644
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -298,7 +298,10 @@ var PingPicker = {
.addEventListener("click", () => this._movePingIndex(-1), false);
document.getElementById("older-ping")
.addEventListener("click", () => this._movePingIndex(1), false);
-
+ document.getElementById("show-raw-ping")
+ .addEventListener("click", () => this._showRawPingData(), false);
+ document.getElementById("hide-raw-ping")
+ .addEventListener("click", () => this._hideRawPingData(), false);
document.getElementById("choose-payload")
.addEventListener("change", () => displayPingData(gPingData), false);
},
@@ -448,6 +451,16 @@ var PingPicker = {
this._renderPingList(ping.id);
this._updateArchivedPingData();
},
+
+ _showRawPingData: function() {
+ let pre = document.getElementById("raw-ping-data");
+ pre.textContent = JSON.stringify(gPingData, null, 2);
+ document.getElementById("raw-ping-data-section").classList.remove("hidden");
+ },
+
+ _hideRawPingData: function() {
+ document.getElementById("raw-ping-data-section").classList.add("hidden");
+ },
};
var GeneralData = {
diff --git a/toolkit/content/aboutTelemetry.xhtml b/toolkit/content/aboutTelemetry.xhtml
index e6e2ab4a906..3754dcdf775 100644
--- a/toolkit/content/aboutTelemetry.xhtml
+++ b/toolkit/content/aboutTelemetry.xhtml
@@ -58,6 +58,7 @@
&aboutTelemetry.showArchivedPingData;
+
&aboutTelemetry.showSubsessionData;
@@ -96,6 +97,11 @@
+
+
✖
+
+
+
&aboutTelemetry.generalDataSection;
diff --git a/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd b/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd
index 58771cca3d3..c68e9042016 100644
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd
@@ -52,6 +52,10 @@ Choose ping:
Older ping >>
">
+
+
From 202aba9a45ea4fb518219b6675b4d7b168cfd583 Mon Sep 17 00:00:00 2001
From: rthyberg
Date: Fri, 6 Nov 2015 03:54:00 +0100
Subject: [PATCH 12/71] Bug 1190801 - Moved loadSessionData and split
saveSessionData from TelemetrySession to TelemetryStorage. r=gfritzsche
---
.../components/telemetry/TelemetrySession.jsm | 154 ++----------------
.../components/telemetry/TelemetryStorage.jsm | 76 ++++++++-
2 files changed, 86 insertions(+), 144 deletions(-)
diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm
index c7751af36e5..c07c76736fd 100644
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -63,8 +63,6 @@ const MESSAGE_TELEMETRY_GET_CHILD_PAYLOAD = "Telemetry:GetChildPayload";
const DATAREPORTING_DIRECTORY = "datareporting";
const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
-const SESSION_STATE_FILE_NAME = "session-state.json";
-
// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
@@ -142,8 +140,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
"resource://gre/modules/UITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
"resource://gre/modules/TelemetryEnvironment.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
- "resource://services-common/utils.js");
function generateUUID() {
let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
@@ -263,97 +259,6 @@ var processInfo = {
}
};
-/**
- * This object allows the serialisation of asynchronous tasks. This is particularly
- * useful to serialise write access to the disk in order to prevent race conditions
- * to corrupt the data being written.
- * We are using this to synchronize saving to the file that TelemetrySession persists
- * its state in.
- */
-function SaveSerializer() {
- this._queuedOperations = [];
- this._queuedInProgress = false;
- this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
-}
-
-SaveSerializer.prototype = {
- /**
- * Enqueues an operation to a list to serialise their execution in order to prevent race
- * conditions. Useful to serialise access to disk.
- *
- * @param {Function} aFunction The task function to enqueue. It must return a promise.
- * @return {Promise} A promise resolved when the enqueued task completes.
- */
- enqueueTask: function (aFunction) {
- let promise = new Promise((resolve, reject) =>
- this._queuedOperations.push([aFunction, resolve, reject]));
-
- if (this._queuedOperations.length == 1) {
- this._popAndPerformQueuedOperation();
- }
- return promise;
- },
-
- /**
- * Make sure to flush all the pending operations.
- * @return {Promise} A promise resolved when all the pending operations have completed.
- */
- flushTasks: function () {
- let dummyTask = () => new Promise(resolve => resolve());
- return this.enqueueTask(dummyTask);
- },
-
- /**
- * Pop a task from the queue, executes it and continue to the next one.
- * This function recursively pops all the tasks.
- */
- _popAndPerformQueuedOperation: function () {
- if (!this._queuedOperations.length || this._queuedInProgress) {
- return;
- }
-
- this._log.trace("_popAndPerformQueuedOperation - Performing queued operation.");
- let [func, resolve, reject] = this._queuedOperations.shift();
- let promise;
-
- try {
- this._queuedInProgress = true;
- promise = func();
- } catch (ex) {
- this._log.warn("_popAndPerformQueuedOperation - Queued operation threw during execution. ",
- ex);
- this._queuedInProgress = false;
- reject(ex);
- this._popAndPerformQueuedOperation();
- return;
- }
-
- if (!promise || typeof(promise.then) != "function") {
- let msg = "Queued operation did not return a promise: " + func;
- this._log.warn("_popAndPerformQueuedOperation - " + msg);
-
- this._queuedInProgress = false;
- reject(new Error(msg));
- this._popAndPerformQueuedOperation();
- return;
- }
-
- promise.then(result => {
- this._log.trace("_popAndPerformQueuedOperation - Queued operation completed.");
- this._queuedInProgress = false;
- resolve(result);
- this._popAndPerformQueuedOperation();
- },
- error => {
- this._log.warn("_popAndPerformQueuedOperation - Failure when performing queued operation.",
- error);
- this._queuedInProgress = false;
- reject(error);
- this._popAndPerformQueuedOperation();
- });
- },
-};
-
/**
* TelemetryScheduler contains a single timer driving all regularly-scheduled
* Telemetry related jobs. Having a single place with this logic simplifies
@@ -762,8 +667,6 @@ var Impl = {
_delayedInitTask: null,
// The deferred promise resolved when the initialization task completes.
_delayedInitTaskDeferred: null,
- // Used to serialize session state writes to disk.
- _stateSaveSerializer: new SaveSerializer(),
get _log() {
if (!this._logger) {
@@ -1332,7 +1235,7 @@ var Impl = {
this.startNewSubsession();
// Persist session data to disk (don't wait until it completes).
let sessionData = this._getSessionDataObject();
- this._stateSaveSerializer.enqueueTask(() => this._saveSessionData(sessionData));
+ TelemetryStorage.saveSessionData(sessionData);
}
return payload;
@@ -1446,7 +1349,7 @@ var Impl = {
yield this._loadSessionData();
// Update the session data to keep track of new subsessions created before
// the initialization.
- yield this._saveSessionData(this._getSessionDataObject());
+ yield TelemetryStorage.saveSessionData(this._getSessionDataObject());
this.attachObservers();
this.gatherMemory();
@@ -1796,7 +1699,6 @@ var Impl = {
if (Telemetry.isOfficialTelemetry || testing) {
return Task.spawn(function*() {
yield this.saveShutdownPings();
- yield this._stateSaveSerializer.flushTasks();
if (IS_UNIFIED_TELEMETRY) {
yield TelemetryController.removeAbortedSessionPing();
@@ -1857,40 +1759,23 @@ var Impl = {
return promise;
},
- /**
- * Loads session data from the session data file.
- * @return {Promise} A promise which is resolved with a true argument when
- * loading has completed, with false otherwise.
+ /** Loads session data from the session data file.
+ * @return {Promise