UNPKG

opentok-annotation

Version:
530 lines (451 loc) 19.3 kB
/* global OT OTSolution OTKAnalytics ScreenSharingAccPack define */ (function () { /** Include external dependencies */ var _; var $; var OTKAnalytics; if (typeof module === 'object' && typeof module.exports === 'object') { /* eslint-disable import/no-unresolved */ _ = require('underscore'); $ = require('jquery'); OTKAnalytics = require('opentok-solutions-logging'); /* eslint-enable import/no-unresolved */ } else { _ = this._; $ = this.$; OTKAnalytics = this.OTKAnalytics; } /** Private variables */ var _this; var _accPack; var _session; var _canvas; var _subscribingToMobileScreen; var _elements = {}; /** Analytics */ var _otkanalytics; // vars for the analytics logs. Internal use var _logEventData = { clientVersion: 'js-vsol-x.y.z', // x.y.z filled by npm build script componentId: 'annotationsAccPack', name: 'guidAnnotationsKit', actionInitialize: 'Init', actionStart: 'Start', actionEnd: 'End', actionFreeHand: 'FreeHand', actionPickerColor: 'PickerColor', actionText: 'Text', actionScreenCapture: 'ScreenCapture', actionErase: 'Erase', actionUseToolbar: 'UseToolbar', variationAttempt: 'Attempt', variationError: 'Failure', variationSuccess: 'Success', }; var _logAnalytics = function () { // init the analytics logs var _source = window.location.href; var otkanalyticsData = { clientVersion: _logEventData.clientVersion, source: _source, componentId: _logEventData.componentId, name: _logEventData.name }; _otkanalytics = new OTKAnalytics(otkanalyticsData); var sessionInfo = { sessionId: _session.id, connectionId: _session.connection.connectionId, partnerId: _session.apiKey }; _otkanalytics.addSessionInfo(sessionInfo); }; var _log = function (action, variation) { var data = { action: action, variation: variation }; _otkanalytics.logEvent(data); }; /** End Analytics */ // Check for DOM element or string. Return element. var _getElem = function (el) { return typeof el === 'string' ? document.querySelector(el) : el; }; // Trigger event via common layer API var _triggerEvent = function (event, data) { if (_accPack) { _accPack.triggerEvent(event, data); } }; var _registerEvents = function () { if (_accPack) { var events = [ 'startAnnotation', 'linkAnnotation', 'resizeCanvas', 'annotationWindowClosed', 'endAnnotation' ]; _accPack.registerEvents(events); } }; var _setupUI = function () { var toolbar = [ '<div id="annotationToolbarContainer" class="ots-annotation-toolbar-container">', '<div id="toolbar"></div>', '</div>' ].join('\n'); $('body').append(toolbar); _log(_logEventData.actionUseToolbar, _logEventData.variationSuccess); }; var _palette = [ '#1abc9c', '#2ecc71', '#3498db', '#9b59b6', '#8e44ad', '#f1c40f', '#e67e22', '#e74c3c', '#ded5d5' ]; var _aspectRatio = (10 / 6); /** Private methods */ var _refreshCanvas = _.throttle(function () { _canvas.onResize(); }, 1000); /** Resize the canvas to match the size of its container */ var _resizeCanvas = _.throttle(function () { var width; var height; var cobrowsing = !!_elements.cobrowsingImage; if (cobrowsing) { // Cobrowsing images are currently fixed size, so resize isn't needed return; } if (_elements.cobrowsingImage === null) { var el = _elements.absoluteParent || _elements.canvasContainer; width = el.clientWidth; height = el.clientHeight; } try { var videoDimensions = _canvas.videoFeed.stream.videoDimensions; } catch (e) { console.log('OT Annotation: Annotation video stream no longer exists'); return; } // Override dimensions when subscribing to a mobile screen if (_subscribingToMobileScreen) { videoDimensions.width = width; videoDimensions.height = height; } var origRatio = videoDimensions.width / videoDimensions.height; var destRatio = width / height; var calcDimensions = { top: 0, left: 0, height: height, width: width }; if (!_elements.externalWindow) { if (origRatio < destRatio) { // height is the limiting prop, we'll get vertical bars calcDimensions.width = calcDimensions.height * origRatio; calcDimensions.left = (width - calcDimensions.width) / 2; } else { calcDimensions.height = calcDimensions.width / origRatio; calcDimensions.top = (height - calcDimensions.height) / 2; } } $(_elements.canvasContainer).find('canvas').css(calcDimensions); $(_elements.canvasContainer).find('canvas').attr(calcDimensions); _refreshCanvas(); _triggerEvent('resizeCanvas'); }, 500, {trailing: false}); var _changeColorByIndex = function (colorIndex) { _canvas.changeColorByIndex(colorIndex); }; var _takeScreenShot = function () { _canvas.captureScreenshot(true); }; var _listenForResize = function () { $(_elements.resizeSubject).on('resize', _resizeCanvas); }; var _createToolbar = function (session, options, externalWindow) { _setupUI(); var toolbarId = _.property('toolbarId')(options) || 'toolbar'; var items = _.property('toolbarItems')(options) || []; var shapes = _.property('toolbarShapes')(options) || []; var colors = _.property('colors')(options) || _palette; var imageAssets = _.property('imageAssets')(options) || null; var backgroundColor = _.property('backgroundColor')(options) || null; var container = function () { var w = !!externalWindow ? externalWindow : window; return w.document.getElementById(toolbarId); }; /* eslint-disable no-native-reassign */ toolbar = new OTSolution.Annotations.Toolbar({ session: session, container: container(), colors: colors, items: items.length ? items : ['*'], shapes: shapes.length ? shapes : ['rectangle', 'oval', 'star', 'arrow', 'line'], externalWindow: externalWindow || null, imageAssets: imageAssets, backgroundColor: backgroundColor, OTKAnalytics: OTKAnalytics }); toolbar.itemClicked(function (id) { var actions = { OT_pen: _logEventData.actionFreeHand, OT_colors: _logEventData.actionPickerColor, OT_text: _logEventData.actionText, OT_clear: _logEventData.actionErase }; var action = actions[id]; if (!!action) { _log(action, _logEventData.variationSuccess); } }); /* eslint-enable no-native-reassign */ }; // Create external screen sharing window var _createExternalWindow = function () { var deferred = $.Deferred(); var width = screen.width * 0.80 | 0; var height = width / (_aspectRatio); var externalWindowHTML = '<!DOCTYPE html><html lang="en"><head><meta http-equiv="Content-type" content="text/html; charset=utf-8"><title>OpenTok Screen Sharing Solution Annotation</title><link rel="stylesheet" href="https://assets.tokbox.com/solutions/css/style.css"><style type="text/css" media="screen"> body{margin:0;background-color:rgba(0,153,203,.7);box-sizing:border-box;height:100vh}canvas{top:0;z-index:1000}.hidden{display:none}.ots-hidden{display:none !important}.main-wrap{width:100%;height:100%;-ms-box-orient:horizontal;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-moz-flex;display:-webkit-flex;display:flex;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;align-items:center}.inner-wrap{position:relative;border-radius:8px;overflow:hidden}.publisherContainer{display:block;background-color:#000;position:absolute}.publisher-wrap{height:100%;width:100%}.subscriberContainer{position:absolute;display:flex;top:20px;left:20px;width:200px;height:120px;background-color:#000;border:2px solid #fff;border-radius:6px}.subscriberContainer .OT_video-poster{width:100%;height:100%;opacity:.25;background-repeat:no-repeat;background-image:url(https://static.opentok.com/webrtc/v2.8.2/images/rtc/audioonly-silhouette.svg);background-size:50%;background-position:center}.OT_video-element{height:100%;width:100%}.OT_edge-bar-item{display:none}</style></head><body> <div class="main-wrap"> <div id="annotationContainer" class="inner-wrap"></div></div><div id="toolbarContainer" class="ots-annotation-toolbar-container"> <div id="toolbar" class="toolbar-wrap"></div></div><div id="subscriberVideo" class="subscriberContainer hidden"></div><script type="text/javascript" charset="utf-8"> /** Must use double-quotes since everything must be converted to a string */ var opener; var canvas; if (!toolbar){alert("Something went wrong: You must pass an OpenTok annotation toolbar object into the window.")}else{opener=window.opener; window.onbeforeunload=window.triggerCloseEvent;}var localScreenProperties={insertMode: "append", width: "100%", height: "100%", videoSource: "window", showControls: false, style:{buttonDisplayMode: "off"}, subscribeToVideo: "true", subscribeToAudio: "false", fitMode: "contain"}; var createContainerElements=function(){var parentDiv=document.getElementById("annotationContainer"); var publisherContainer=document.createElement("div"); publisherContainer.setAttribute("id", "screenshare_publisher"); publisherContainer.classList.add("publisher-wrap"); parentDiv.appendChild(publisherContainer); return{annotation: parentDiv, publisher: publisherContainer};}; var addSubscriberVideo=function(stream){var container=document.getElementById("subscriberVideo"); var subscriber=session.subscribe(stream, container, localScreenProperties, function(error){if (error){console.log("Failed to add subscriber video", error);}container.classList.remove("hidden");});}; if (navigator.userAgent.indexOf("Firefox") !==-1){var ghost=window.open("about:blank"); ghost.focus(); ghost.close();}</script></body></html>'; /* eslint-disable max-len */ var windowFeatures = [ 'toolbar=no', 'location=no', 'directories=no', 'status=no', 'menubar=no', 'scrollbars=no', 'resizable=no', 'copyhistory=no', ['width=', width].join(''), ['height=', height].join(''), ['left=', ((screen.width / 2) - (width / 2))].join(''), ['top=', ((screen.height / 2) - (height / 2))].join('') ].join(','); /* eslint-enable max-len */ var annotationWindow = window.open('about:blank', '', windowFeatures); annotationWindow.document.write(externalWindowHTML); window.onbeforeunload = function () { annotationWindow.close(); }; // External window needs access to certain globals annotationWindow.toolbar = toolbar; annotationWindow.OT = OT; annotationWindow.session = _session; annotationWindow.$ = $; annotationWindow.triggerCloseEvent = function () { _triggerEvent('annotationWindowClosed'); }; annotationWindow.onbeforeunload = function () { _triggerEvent('annotationWindowClosed'); }; // TODO Find something better. var windowReady = function () { if (!!annotationWindow.createContainerElements) { $(annotationWindow.document).ready(function () { deferred.resolve(annotationWindow); }); } else { setTimeout(windowReady, 100); } }; windowReady(); return deferred.promise(); }; // Remove the toolbar and cancel event listeners var _removeToolbar = function () { _canvas.onClearAnnotation(); $(_elements.resizeSubject).off('resize', _resizeCanvas); toolbar.remove(); $('#annotationToolbarContainer').remove(); }; // Determine whether or not the subscriber stream is from a mobile device var _requestPlatformData = function (pubSub, mobileInitiator) { if (!!pubSub.stream) { var isPublisher = Object.prototype.hasOwnProperty.call(pubSub, 'accessAllowed'); _session.signal({ type: 'otAnnotation_requestPlatform', to: pubSub.stream.connection, }); _session.on('signal:otAnnotation_mobileScreenShare', function (event) { var platform = event.data ? JSON.parse(event.data).platform : null; var isMobile = (platform == 'ios' || platform === 'android'); _subscribingToMobileScreen = isMobile; _canvas.onMobileScreenShare(isMobile, isPublisher); }); } if (mobileInitiator) { _subscribingToMobileScreen = true; _canvas.onMobileScreenShare(true); } }; /** * Creates an external window (if required) and links the annotation toolbar * to the session * @param {object} session * @param {object} [options] * @param {boolean} [options.screensharing] - Using an external window * @param {string} [options.toolbarId] - If the container has an id other than 'toolbar' * @param {array} [options.items] - Custom set of tools * @param {array} [options.colors] - Custom color palette * @returns {promise} < Resolve: undefined | {object} Reference to external annotation window > */ var start = function (session, options) { var deferred = $.Deferred(); if (_.property('screensharing')(options)) { _createExternalWindow() .then(function (externalWindow) { _createToolbar(session, options, externalWindow); toolbar.createPanel(externalWindow); _triggerEvent('startAnnotation', externalWindow); _log(_logEventData.actionStart, _logEventData.variationSuccess); deferred.resolve(externalWindow); }); } else { _createToolbar(session, options); _triggerEvent('startAnnotation'); _log(_logEventData.actionStart, _logEventData.variationSuccess); deferred.resolve(); } return deferred.promise(); }; /** * @param {object} pubSub - Either the publisher(sharing) or subscriber(viewing) * @ param {object} container - The parent container for the canvas element * @ param {object} options * @param {object} options.canvasContainer - The id of the parent for the annotation canvas * @param {object | string} [options.externalWindow] - Reference to the annotation window (or query selector) if publishing * @param {array | string} [options.absoluteParent] - Reference to element (or query selector) for resize if other than container * * @param {Boolean} [options.mobileInitiator] - Is cobrowsing being initiated by a mobile device */ var linkCanvas = function (pubSub, container, options) { /** * jQuery only allows listening for a resize event on the window or a * jQuery resizable element, like #wmsFeedWrap. windowRefernce is a * reference to the popup window created for annotation. If this doesn't * exist, we are watching the canvas belonging to the party viewing the * shared screen */ _elements.resizeSubject = _getElem(_.property('externalWindow')(options) || window); _elements.externalWindow = _getElem(_.property('externalWindow')(options) || null); _elements.absoluteParent = _getElem(_.property('absoluteParent')(options) || null); _elements.cobrowsingImage = _getElem(_.property('cobrowsingImage')(options) || null); _elements.canvasContainer = _getElem(container); // The canvas object _canvas = new OTSolution.Annotations({ feed: pubSub, container: _elements.canvasContainer, externalWindow: _elements.externalWindow }); toolbar.addCanvas(_canvas, _elements.externalWindow); var onScreenCapture = _this.options.onScreenCapture ? _this.options.onScreenCapture : function (dataUrl) { var win = window.open(dataUrl, '_blank'); win.focus(); }; _canvas.onScreenCapture(onScreenCapture); _requestPlatformData(pubSub, options && options.mobileInitiator); var context = _elements.externalWindow ? _elements.externalWindow : window; // The canvas DOM element _elements.canvas = $(_.first(context.document.getElementsByTagName('canvas'))); _listenForResize(); _resizeCanvas(); _triggerEvent('linkAnnotation'); }; /** * Manually update the size of the canvas to match it's container, or the * absolute parent, if defined. */ var resizeCanvas = function () { _resizeCanvas(); }; /** * Change the annotation color of the toolbar passing the colorIndex * @param {Integer} colorIndex - The color index number */ var changeColorByIndex = function (colorIndex) { _changeColorByIndex(colorIndex); }; var takeScreenShot = function () { _takeScreenShot(); }; /** * Adds a subscriber's video the external annotation window * @param {Object} stream - The subscriber stream object */ var addSubscriberToExternalWindow = function (stream) { if (!_elements.externalWindow) { console.log('OT Annotation: External window does not exist. Cannot add subscriber video.'); } else { _elements.externalWindow.addSubscriberVideo(stream); } }; /** * Stop annotation and clean up components * @param {Boolean} publisher Are we the publisher? */ var end = function () { _removeToolbar(); _elements.canvas = null; if (!!_elements.externalWindow) { _elements.externalWindow.close(); _elements.externalWindow = null; _elements.resizeSubject = null; } _triggerEvent('endAnnotation'); _log(_logEventData.actionEnd, _logEventData.variationSuccess); }; var hideToolbar = function () { $(toolbar.parent).hide(); }; var showToolbar = function () { $(toolbar.parent).show(); }; /** * @constructor * Represents an annotation component, used for annotation over video or a shared screen * @param {object} options * @param {object} options.session - An OpenTok session * @param {object} options.canvasContainer - The id of the parent for the annotation canvas * @param {object} options.watchForResize - The DOM element to watch for resize * @param {object} options.onScreenCapture- A callback function to be invoked on screen capture */ var AnnotationAccPack = function (options) { _this = this; _this.options = _.omit(options, 'accPack', 'session'); _accPack = _.property('accPack')(options); _session = _.property('session')(options); if (!_session) { throw new Error('OpenTok Annotation Accelerator Pack requires an OpenTok session'); } _registerEvents(); // init analytics logs _logAnalytics(); _log(_logEventData.actionInitialize, _logEventData.variationSuccess); }; AnnotationAccPack.prototype = { constructor: AnnotationAccPack, start: start, linkCanvas: linkCanvas, resizeCanvas: resizeCanvas, addSubscriberToExternalWindow: addSubscriberToExternalWindow, end: end, hideToolbar: hideToolbar, showToolbar: showToolbar, changeColorByIndex: changeColorByIndex, takeScreenShot: takeScreenShot }; if (typeof exports === 'object') { module.exports = AnnotationAccPack; } else if (typeof define === 'function' && define.amd) { define(function () { return AnnotationAccPack; }); } else { this.AnnotationAccPack = AnnotationAccPack; } }.call(this));