/*! * OpenTok JavaScript Library v0.91.46 * http://www.tokbox.com/ * * Copyright (c) 2011 TokBox, Inc. * * Date: February 16 18:15:10 2012 */ function getHostname() { return window.location.hostname; } // Instrumentation TB = function() { //-------------------------------------- // EVENT CLASSES //-------------------------------------- function EventDispatcher() { this._listeners = {}; this.addEventListener = function(type, listener) { if (!type) { throw new Error("EventDispatcher.addEventListener :: No type specified"); } if (!listener) { throw new Error("EventDispatcher.addEventListener :: No listener function specified"); } if (!this._listeners.hasOwnProperty(type)) { this._listeners[type] = new Array(); } this.removeEventListener(type, listener); // You cannot have the same listener for the same type multiple times debug("TB.addEventListener(" + type + ")"); this._listeners[type].push(listener); }; this.removeEventListener = function(type, listener) { if (!type) { throw new Error("EventDispatcher.removeEventListener :: No type specified"); } if (!listener) { throw new Error("EventDispatcher.removeEventListener :: No listener function specified"); } debug("TB.removeEventListener(" + type + ")"); if (this._listeners.hasOwnProperty(type)) { for (var i=0; i < this._listeners[type].length; i++) { if (this._listeners[type][i] == listener) { this._listeners[type].splice(i, 1); break; } }; } }; this.dispatchEvent = function(event) { if (!event) { throw new Error("EventDispatcher.dispatchEvent :: No event specified"); } if (!event.type) { throw new Error("EventDispatcher.dispatchEvent :: Event has no type"); } if (!event.target) { event.target = this; } if (this._listeners.hasOwnProperty(event.type)) { var listeners = this._listeners[event.type]; if (listeners instanceof Array) { for (var i=0; i < listeners.length; i++) { var handler = createHandler(listeners[i], event); // We run this asynchronously so that it doesn't interfere with execution if an error happens // eg. multiple event handlers are added one has an error so the subsequent ones fail setTimeout(handler, 1); }; } else { throw new Error("EventDispatcher.dispatchEvent :: Invalid object type in listeners"); } } }; } function Event (type, cancelable) { this.type = type; this.cancelable = cancelable ? cancelable : false; this.target; var defaultPrevented = false; this.preventDefault = function() { if (this.cancelable) { defaultPrevented = true; } else { warn("Event.preventDefault :: Trying to preventDefault on an Event that isn't cancelable"); } }; this.isDefaultPrevented = function() { return defaultPrevented; }; } function ExceptionEvent (type, message, title, code) { this.superClass = Event; this.superClass(type); this.message = message; this.title = title; this.code = code; } function ConnectionEvent (type, connections, reason) { this.superClass = Event; this.superClass(type); this.connections = connections; this.reason = reason; } function StreamEvent (type, streams, reason, cancelable) { this.superClass = Event; this.superClass(type, cancelable); this.streams = streams; this.reason = reason; } function SessionConnectEvent (type, connections, streams, groups, archives) { this.superClass = Event; this.superClass(type); this.connections = connections; this.streams = streams; this.groups = groups; this.archives = archives; } function SessionDisconnectEvent (type, reason, cancelable) { this.superClass = Event; this.superClass(type, cancelable); this.reason = reason; } function SignalEvent (type, fromConnection) { this.superClass = Event; this.superClass(type); this.fromConnection = fromConnection; } function VolumeEvent(type, streamId, volume) { this.superClass = Event; this.superClass(type); this.streamId = streamId; this.volume = volume; } function DeviceEvent (type, camera, microphone) { this.superClass = Event; this.superClass(type); this.camera = camera; this.microphone = microphone; } function GroupEvent (type, group, reason) { this.superClass = Event; this.superClass(type); this.group = group; this.reason = reason; } function DeviceStatusEvent (type, cameras, microphones, selectedCamera, selectedMicrophone) { this.superClass = Event; this.superClass(type); this.cameras = cameras; this.microphones = microphones; this.selectedCamera = selectedCamera; this.selectedMicrophone = selectedMicrophone; } function ResizeEvent (type, widthFrom, widthTo, heightFrom, heightTo) { this.superClass = Event; this.superClass(type); this.widthFrom = widthFrom; this.widthTo = widthTo; this.heightFrom = heightFrom; this.heightTo = heightTo; } function StreamPropertyChangedEvent(type, stream, changedProperty, oldValue, newValue) { this.superClass = Event; this.superClass(type); this.type = type; this.stream = stream; this.changedProperty = changedProperty; this.oldValue = oldValue; this.newValue = newValue; } function ArchiveEvent (type, archives) { this.superClass = Event; this.superClass(type); this.archives = archives; } function ArchiveStreamEvent (type, archive, streams) { this.superClass = Event; this.superClass(type); this.archive = archive; this.streams = streams; } function StateChangedEvent(type, changedValues) { this.superClass = Event; this.superClass(type); this.changedValues = changedValues; } function ChangeFailedEvent(type, reasonCode, reason, failedValues) { this.superClass = Event; this.superClass(type); this.reasonCode = reasonCode; this.reason = reason; this.failedValues = failedValues; } //-------------------------------------- // CLASSES //-------------------------------------- function Connection (connectionId, creationTime, data) { this.connectionId = connectionId; this.creationTime = Number(creationTime); this.data = data; this.quality; } function Stream (streamId, connection, name, data, type, creationTime, hasAudio, hasVideo, orientation, sessionId, peerId, quality) { //INSTANCE VARIABLES this.streamId = streamId; this.connection = connection; this.name = name; this.data = data; this.type = type; this.creationTime = creationTime; this.hasAudio = hasAudio; this.hasVideo = hasVideo; this.orientation = orientation; this.peerId = peerId; this.quality = quality; this.startRecording = function(archive) { debug("Stream.startRecording()"); var controllerId = "controller_" + sessionId; archive = createdArchives[sessionId][archive.archiveId]; if (!archive) { var errorMsg = "Stream.startRecording :: Archive not created."; error(errorMsg); throw new Error(errorMsg); } if (archive.type != TB.PER_STREAM) { errorMsg = "Stream.startRecording :: Trying to record per stream on a " + archive.type + " archive"; error(errorMsg); throw new Error(errorMsg); } if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.startRecordingStream(this.streamId, archive.archiveId); archive.recording = true; } catch(err) { errorMsg = "Stream.startRecording :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Stream.startRecording :: Connection required to record an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.stopRecording = function(archive) { debug("Stream.stopRecording()"); archive = createdArchives[sessionId][archive.archiveId]; if (!archive) { var errorMsg = "Stream.stopRecording :: Archive not created."; error(errorMsg); throw new Error(errorMsg); } if (archive.type != TB.PER_STREAM) { errorMsg = "Stream.stopRecording :: Trying to stop recording per stream on a " + archive.type + " archive"; error(errorMsg); throw new Error(errorMsg); } var controllerId = "controller_" + sessionId; if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.stopRecordingStream(this.streamId, archive.archiveId); } catch(err) { errorMsg = "Stream.stopRecording :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Stream.stopRecording :: Connection required to record an archive."; error(errorMsg); throw new Error(errorMsg); } }; } function UIComponent (id, replacedDivId) { this.id = id; this.replacedDivId = replacedDivId; this.parentClass = EventDispatcher; this.parentClass(); } function StylableComponent(id, replacedDivId) { this.uberClass = UIComponent; this.uberClass(id, replacedDivId); var componentStyles = ["showMicButton", "showSpeakerButton", "showSettingsButton", "showCameraToggleButton", "nameDisplayMode", "buttonDisplayMode", "showSaveButton", "showRecordButton", "showRecordStopButton", "showReRecordButton", "showPauseButton", "showPlayButton", "showPlayStopButton", "showStopButton", "backgroundImageURI", "showControlPanel", "showRecordCounter", "showPlayCounter", "showControlBar"]; this.getStyle = function(key) { var component = document.getElementById(this.id); if (!this.loaded) { if (key) { return this._style[key]; } else { return this._style; } } else if (component) { try { var style = component.getStyle(key); if (typeof(style) == "string") return style; for (var i in style) { if (style[i] == "false") style[i] = false; if (style[i] == "true") style[i] = true; if (componentStyles.indexOf(i) < 0) { // Strip unnecessary properties out delete style[i]; } }; return style; } catch (err) { var errorMsg = "Publisher.getStyle:: Failed to call getStyle. " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher.getStyle:: Publisher " + this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } }; this._style = {}; var validStyleValues = { buttonDisplayMode: ["auto", "off", "on"], nameDisplayMode: ["auto", "off", "on"], showSettingsButton: [true, false], showMicButton: [true, false], showCameraToggleButton: [true, false], showSaveButton: [true, false], backgroundImageURI: null, showControlBar: [true, false], showPlayCounter: [true, false], showRecordCounter: [true, false] }; this.setStyle = function(key, value) { debug("Publisher.setStyle: " + key.toString()); var component = document.getElementById(this.id); if (!this.loaded) { if ((typeof(key) == "string") && value != null) { if (this._style.hasOwnProperty(key) && (key == "backgroundImageURI" || (validStyleValues[key].indexOf(value) > -1)) ) { debug("setStyle::Setting " + key + " to " + value); this._style[key] = value; } else { warn("setStyle::Invalid style property passed " + key + " : " + value); } } else { for (var i in key) { this.setStyle(i, key[i]); }; } this.modified = true; } else if (component) { try { component.setStyle(key, value); } catch (err) { var errorMsg = "Publisher.setStyle:: Failed to call setStyle. " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher.setStyle:: Publisher " + this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } return this; }; } function VideoComponent(id, replacedDivId) { this.supClass = StylableComponent; this.supClass(id, replacedDivId); this.getImgData = function() { debug("VideoComponent.getImgData"); var component = document.getElementById(this.id); if (component) { try { return component.getImgData(); } catch(err) { var errorMsg = "VideoComponent.getImgData:: Failed to call getImgData. " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "VideoComponent.getImgData:: Component " + this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } }; } function Publisher (id, replacedDivId, properties) { this.superClass = VideoComponent; this.superClass(id, replacedDivId); this._style = { showMicButton: true, showSettingsButton: true, showCameraToggleButton: true, nameDisplayMode: "auto", buttonDisplayMode: "auto", backgroundImageURI: null }; this.modified = false; if (properties && properties.hasOwnProperty("style")) { this.setStyle(properties['style']); this.modified = true; } this.properties = properties; this.loaded = false; this.panelId = null; this.gain = 50; if(properties && properties.hasOwnProperty("microphoneGain")) { this.gain = parseInt(properties["microphoneGain"], 10); } this.enableMicrophone = function() { this.publishAudio(true); }; this.disableMicrophone = function() { this.publishAudio(false); }; this.setMicrophoneGain = function(value) { var component = document.getElementById(this.id); if (!this.loaded) { this.gain = value; this.modified = true; } else if (component) { try { component.setMicGain(value); } catch (err) { var errorMsg = "Microphone gain adjustment on publisher "+this.id+" failed"; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } return this; }; this.getMicrophoneGain = function() { var component = document.getElementById(this.id); if (!this.loaded) { return this.gain; } else if (component) { try { return component.getMicGain(); } catch (err) { var errorMsg = "Microphone gain adjustment on publisher "+this.id+" failed"; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } }; this.getEchoCancellationMode = function() { debug("Publisher.getEchoCancellationMode()"); var mode = ""; var component = document.getElementById(this.id); if (!this.loaded) { return "unknown"; } else if (component) { try { mode = component.getEchoCancellationMode(); } catch (err) { var errorMsg = "Getting echo cancellation mode for publisher " + this.id + " failed. " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } return mode; }; this.publishAudio = function(publishAudioBool) { debug("Publisher.publishAudio()"); if (!this.loaded) { this.audioPublished = publishAudioBool; this.modified = true; } else { setStreamProperty(this.id, "publishAudio", publishAudioBool); } }; this.publishVideo = function(publishVideoBool) { debug("Publisher.publishVideo()"); if (!this.loaded) { this.videoPublished = publishVideoBool; this.modified = true; } else { setStreamProperty(this.id, "publishVideo", publishVideoBool); } }; this.setCamera = function(camera) { // Private function debug("Publisher.setCamera(" + camera + ")"); setDevice(this.id, camera, true); }; this.setMicrophone = function(microphone) { // Private function debug("Publisher.setMicrophone(" + microphone + ")"); setDevice(this.id, microphone, false); }; } function Subscriber (stream, id, replacedDivId, properties) { this.superClass = VideoComponent; this.superClass(id, replacedDivId); this._style = { nameDisplayMode: "auto", buttonDisplayMode: "auto", backgroundImageURI: null }; this.modified = false; if (properties && properties.hasOwnProperty("style")) { this.setStyle(properties['style']); this.modified = true; } this.stream = stream; this.properties = properties; this.loaded = false; this.audioVolume = 50; var _isAudioSubscribed = true; var _isVideoSubscribed = true; if(properties) { if(properties.hasOwnProperty("subscribeToAudio") && (properties["subscribeToAudio"] == "false" || properties["subscribeToAudio"] == false)) { _isAudioSubscribed = false; } if(properties.hasOwnProperty("subscribeToVideo") && (properties["subscribeToVideo"] == "false" || properties["subscribeToVideo"] == false)) { _isVideoSubscribed = false; } if(properties.hasOwnProperty("audioVolume")) { this.audioVolume = parseInt(properties["audioVolume"], 10); } } this.enableAudio = function() { this.subscribeToAudio(true); }; this.disableAudio = function() { this.subscribeToAudio(false); }; this.setAudioVolume = function(value) { var component = document.getElementById(this.id); if (!this.loaded) { this.audioVolume = value; } else if (component) { try { component.setAudioVolume(value); } catch (err) { var errorMsg = "Volume adjustment on subscriber "+this.id+" failed"; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Subscriber "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } return this; }; this.getAudioVolume = function() { var component = document.getElementById(this.id); if (!this.loaded) { return this.audioVolume; } if (component) { try { return component.getAudioVolume(); } catch (err) { var errorMsg = "Volume adjustment on subscriber "+this.id+" failed"; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Subscriber "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } return this; }; /** * Internal function to toggle the subscribeToAudio that respects * the developer's state of subscribing */ this._subscribeToAudio = function(subscribeAudioBool, isTokBox) { debug("Subscriber.subscribeToAudio()"); if(!isTokBox || _isAudioSubscribed) { if(!this.loaded) { this.audioSubscribed = subscribeAudioBool; this.modified = true; } else { setStreamProperty(this.id, "subscribeToAudio", subscribeAudioBool); } } }; this.subscribeToAudio = function(subscribeAudioBool) { _isAudioSubscribed = subscribeAudioBool; this._subscribeToAudio(_isAudioSubscribed, false); }; /** * Internal function to toggle the subscribeToVideo that respects * the developer's state of subscribing */ this._subscribeToVideo = function(subscribeVideoBool, isTokBox) { debug("Subscriber.subscribeToVideo()"); if(!isTokBox || _isVideoSubscribed) { if(!this.loaded) { this.videoSubscribed = subscribeVideoBool; this.modified = true; } else { setStreamProperty(this.id, "subscribeToVideo", subscribeVideoBool); } } }; this.subscribeToVideo = function(subscribeVideoBool) { _isVideoSubscribed = subscribeVideoBool; this._subscribeToVideo(_isVideoSubscribed, false); }; this.changeOrientation = function(orientation) { // private function debug("Subscriber.changeOrientation()"); setStreamProperty(this.id, "changeOrientation", orientation); }; } function DevicePanel (id, replacedDivId, component, properties) { this.superClass = UIComponent; this.superClass(id, replacedDivId); if (component) { this.publisher = component; //publisher is deprecated this.component = component; } else { this.publisher = null; this.component = component; } this.parentCreated = false; this.properties = properties; } function Camera (name, status) { this.name = name; this.status = status; } function Microphone (name, status) { this.name = name; this.status = status; } function Group (sessionId,groupId) { this.superClass = EventDispatcher; this.superClass(); this.groupId = groupId; this.sessionId = sessionId; this.enableEchoSuppression = function() { debug("Group.enableEchoSuppresion()"); setEchoSuppressionEnabled(this.sessionId, this.groupId, true); }; this.disableEchoSuppression = function() { debug("Group.disableEchoSuppression()"); setEchoSuppressionEnabled(this.sessionId, this.groupId, false); }; this.getGroupProperties = function() { debug("Group.getGroupProperties()"); return getGroupProperties(this.sessionId, this.groupId); }; } function EchoSuppression(isEnabled){ this.isEnabled = isEnabled; } function Multiplexer(outputStreams,switchType,switchTimeout) { this.numOutputStreams = outputStreams; this.switchType = switchType; this.switchTimeout = switchTimeout; } function GroupProperties(group) { this.echoSuppression = new EchoSuppression(group.echoSuppressionEnabled); this.multiplexer = new Multiplexer(group.multiplexerNumOutputStreams,group.multiplexerSwitchType,group.multiplexerSwitchTimeout); } function Archive (archiveId, type, title, sessionId, status) { this.archiveId = archiveId; this.type = type; this.title = title; this.sessionId = sessionId; var stateManager; if (status == "sessionRecordingInProgress") { this.recording = true; this.status = "open"; } else { this.recording = false; this.status = status; } this.startPlayback = function(loop) { if (!loop) { loop = false; } debug("Archive.startPlayback() : " + loop); var controllerId = "controller_" + sessionId; var connection = TB.sessions[sessionId].connection; if (!loadedArchives[sessionId][this.archiveId]) { var errorMsg = "Archive.startPlayback :: Archive not loaded."; error(errorMsg); throw new Error(errorMsg); } if (controllerId && connection && connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.startPlayback(this.archiveId, loop); } catch(err) { errorMsg = "Archive.startPlayback :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Archive.startPlayback :: Connection required to play back an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.stopPlayback = function() { debug("Archive.stopPlayback()"); var controllerId = "controller_" + sessionId; var connection = TB.sessions[sessionId].connection; if (controllerId && connection && connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.stopPlayback(this.archiveId); } catch(err) { var errorMsg = "Archive.stopPlayback :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Archive.stopPlayback :: Connection required to stop playing back an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.getStateManager = function() { debug("Archive.getStateManager() " + archiveId); if (stateManager) return stateManager; else { var controllerId = "controller_" + sessionId; var connection = TB.sessions[sessionId].connection; if (controllerId && connection && connection.connectionId) { stateManager = new StateManager(controllerId, archiveId); return stateManager; } } var errorMsg = "Archive.getStateManager :: Connection required to getStateManager. " + "Make sure that this archive was loaded in a Session."; error(errorMsg); throw new Error(errorMsg); }; } function Recorder(id, replacedDivId, properties) { this.superClass = VideoComponent; this.superClass(id, replacedDivId); this._style = { buttonDisplayMode: "auto", showCameraToggleButton: true, showControlBar: true, showMicButton: true, showPlayCounter: true, showRecordCounter: true, showSaveButton: true, showSettingsButton: true }; this.id = id; this.properties = properties; this.saveArchive = function() { var recorderElement = document.getElementById(this.id); recorderElement.save(); }; this.setCamera = function(camera) { debug("Recorder.setCamera(" + camera + ")"); setDevice(this.id, camera, true); }; this.setMicrophone = function(microphone) { debug("Recorder.setMicrophone(" + microphone + ")"); setDevice(this.id, microphone, false); }; this.stopRecording = function() { recorderElement = document.getElementById(this.id); recorderElement.stopRecording(); }; this.startRecording = function(title) { recorderElement = document.getElementById(this.id); recorderElement.startRecording(title); }; this.startPlaying = function() { debug("Recorder.startPlaying()"); try { var recorderElement = document.getElementById(this.id); recorderElement.startPlaying(); } catch(err) { var errorMsg = "Recorder.startPlaying :: " + err; error(errorMsg); throw new Error(errorMsg); } }; this.stopPlaying = function() { debug("Recorder.stopPlaying()"); try { var recorderElement = document.getElementById(this.id); recorderElement.stopPlaying(); } catch(err) { var errorMsg = "Recorder.stopPlaying :: " + err; error(errorMsg); throw new Error(errorMsg); } }; this.setTitle = function (title) { var component = document.getElementById(this.id); if (!this.loaded) { this._title = title; this.modified = true; } else if (component) { try { component.setTitle(title); } catch (err) { var errorMsg = "Setting archive title on Recorder "+this.id+" failed."; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Recorder "+ this.id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } }; } function Player(id, replacedDivId, properties) { this.superClass = VideoComponent; this.superClass(id, replacedDivId); this._style = { showPlayButton: true, showStopButton: true, showSpeakerButton: true }; this.id = id; this.properties = properties; this.archiveId; this.loadArchive = function(archiveId) { if (archiveId) { if (this.loaded) { try { var player = document.getElementById(this.id); player.loadArchive(archiveId); this.archiveId = archiveId; } catch(err) { var errorMsg = "Player.loadArchive :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { this._archiveId = archiveId; } } else { errorMsg = "Player.loadArchive :: Archive id required to load an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.play = function() { if (this.loaded) { try { var player = document.getElementById(this.id); player.startPlayback(); } catch(err) { var errorMsg = "Player.play :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { this._play = true; } }; this.stop = function() { if (this.loaded) { try { var player = document.getElementById(this.id); player.stopPlayback(); } catch(err) { var errorMsg = "Player.stop :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { this._play = false; } }; this.pause = function() { if (this.loaded) { try { var player = document.getElementById(this.id); player.pausePlayback(); } catch(err) { var errorMsg = "Player.pause :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { this._play = false; } }; } function DeviceManager (apiKey) { this.superClass = EventDispatcher; this.superClass(); this.apiKey = apiKey; this.panels = {}; this.showMicSettings = true; this.showCamSettings = true; var DEVICE_PANEL_WIDTH = 360; var DEVICE_PANEL_HEIGHT = 270; var DEVICE_PANEL_WIDTH_NO_CHROME = 340; var DEVICE_PANEL_HEIGHT_NO_CHROME = 230; this.detectDevices = function() { debug("DeviceManager.detectDevices()"); if (!deviceDetectorId) { var params = {}; params.allowscriptaccess = "always"; deviceDetectorId = "opentok_deviceDetector"; var attributes = {}; attributes.id = deviceDetectorId; var properties = {}; swfobject.addDomLoadEvent(function() { var div = document.createElement('div'); div.setAttribute('id', deviceDetectorId); div.style.display = "none"; document.body.appendChild(div); swfobject.embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_devicedetectorwidget.swf?partnerId="+apiKey, deviceDetectorId, 1, 1, "10.0.0", false, properties, params, attributes); }); } else { try { var deviceDetector = document.getElementById(deviceDetectorId); deviceDetector.detectDevices(); } catch(err) { error(err); throw new Error("DeviceManager.detectDevices() :: Failed to locate existing device detector " + err); } } }; this.displayPanel = function(replaceElementId, component, properties) { debug("DeviceManager.displayPanel(" + replaceElementId + ")"); var panelId; if (component) panelId = "displayPanel_" + component.id; else panelId = "displayPanel_global"; // If this is a publisher update the panelId in the publisher object if (component && TB.sessions) { for (var i in TB.sessions) { if (TB.sessions[i].hasOwnProperty("publishers") && TB.sessions[i].publishers[component.id]) { TB.sessions[i].publishers[component.id].panelId = panelId; } } } var existingElement = document.getElementById(panelId); if (existingElement) { warn("DeviceManager.displayPanel :: there is already a device panel" + (component ? " for this component" : "")); return this.panels[panelId]; } var parentCreated = false; var propertiesCopy = (properties) ? copyObject(properties) : {}; var params = {}; params.allowscriptaccess = "always"; var width = DEVICE_PANEL_WIDTH; var height = DEVICE_PANEL_HEIGHT; if ("showCloseButton" in propertiesCopy) { if (propertiesCopy["showCloseButton"] == false) { width = DEVICE_PANEL_WIDTH_NO_CHROME; height = DEVICE_PANEL_HEIGHT_NO_CHROME; } } else { propertiesCopy["showCloseButton"] = true; } if(!("showMicSettings" in propertiesCopy)) { propertiesCopy["showMicSettings"] = this.showMicSettings; } if(!("showCamSettings" in propertiesCopy)) { propertiesCopy["showCamSettings"] = this.showCamSettings; } if(!replaceElementId) { // If they didn't specify a replaceElementId then we will create a new element replaceElementId = 'devicePanel_replace_div'; var replaceDiv = document.createElement('div'); replaceDiv.setAttribute('id', replaceElementId); var parentDiv = document.createElement('div'); parentDiv.setAttribute('id', 'devicePanel_parent_' + (component ? component.id : 'global')); parentDiv.style.position = "absolute"; var yOffset = ("pageYOffset" in window && typeof( window.pageYOffset ) == 'number') ? window["pageYOffset"] : (document.body && document.body.scrollTop) ? document.body.scrollTop : (document.documentElement && document.documentElement.scrollTop) ? document.documentElement.scrollTop : 0; var winHeight = ("innerHeight" in window) ? window.innerHeight : (document.documentElement && document.documentElement.offsetHeight) ? document.documentElement.offsetHeight : DEVICE_PANEL_HEIGHT; yOffset += (winHeight * 0.20); // 20% down the current screen parentDiv.style.top = yOffset + "px"; parentDiv.style.left = "50%"; parentDiv.style.width = width + "px"; parentDiv.style.height = height + "px"; parentDiv.style.marginLeft = (0 - width/2) + "px"; parentDiv.style.marginTop = (0 - height/4) + "px"; if ("zIndex" in propertiesCopy) { parentDiv.style.zIndex = propertiesCopy["zIndex"]; delete propertiesCopy["zIndex"]; } else { parentDiv.style.zIndex = highZ()+1; } document.body.appendChild(parentDiv); parentCreated = true; parentDiv.appendChild(replaceDiv); } var replaceElement = document.getElementById(replaceElementId); if(!replaceElement) { var errorMsg = "DeviceManager.displayPanel :: replaceElementId does not exist in DOM."; error(errorMsg); throw new Error(errorMsg); } var devicePanel; if (this.panels[panelId]) this.removePanel(this.panels[panelId]); if (component) devicePanel = new DevicePanel(panelId, replaceElementId, component, propertiesCopy); else devicePanel = new DevicePanel(panelId, replaceElementId, null, propertiesCopy); devicePanel.parentCreated = parentCreated; this.panels[panelId] = devicePanel; var attributes = {}; attributes.id = devicePanel.id; attributes.style = "outline:none;"; propertiesCopy["devicePanelId"] = panelId; if (propertiesCopy.wmode) { params.wmode = propertiesCopy.wmode; delete propertiesCopy["wmode"]; } else { params.wmode = "transparent"; } embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_devicewidget.swf?partnerId="+this.apiKey, replaceElementId, width, height, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes); return devicePanel; }; this.removePanel = function(devicePanel) { if (!devicePanel.hasOwnProperty("id")) { var errorMsg = "DeviceManager.removePanel :: invalid DevicePanel object"; error(errorMsg); throw new Error(errorMsg); } debug("DeviceManager.removePanel(" + devicePanel.id + ")"); var devicePanelElement = document.getElementById(devicePanel.id); if (!devicePanelElement) { errorMsg = "DeviceManager.removePanel :: DevicePanel does not exist in DOM"; error(errorMsg); throw new Error(errorMsg); } var parentElement = devicePanelElement.parentNode; var parentCreated = devicePanel.parentCreated; for (var dp in this.panels) { if (this.panels[dp].hasOwnProperty("id") && dp == devicePanel.id) { var panel = this.panels[dp]; unloadComponent(this.panels[dp]); delete this.panels[dp]; var action = function() { if (panel.publisher && TB.sessions) { for (var i in TB.sessions) { if (TB.sessions[i].hasOwnProperty("disconnect") && TB.sessions[i].publishers[panel.publisher.id]) { TB.sessions[i].publishers[panel.publisher.id].panelId = null; } } } }; // The event handler is called asynchronously after 2 milliseconds. setTimeout(action, 2); } } if (parentCreated) { // Remove the parent because we created it try { var parentNode = parentElement.parentNode; parentNode.removeChild(parentElement); } catch (err) { errorMsg = "Failed to clean up the parent of the device panel " + err; error(errorMsg); throw new Error(errorMsg); } } }; } function RecorderManager (apiKey) { var recorderCount = 1; var playerCount = 1; this.recorders = {}; this.players = {}; this.apiKey = apiKey; var DEFAULT_WIDTH = 320; var DEFAULT_HEIGHT = 271; var CONTROL_BAR_HEIGHT = 31; this.displayRecorder = function(token, replaceElementId, properties) { if (!token) { errorMsg = "RecorderManager.displayRecorder :: Token required to displayRecorder"; error(errorMsg); throw new Error(errorMsg); } var recorderId = "recorder_" + apiKey + "_" + recorderCount++; var propertiesCopy = (properties) ? copyObject(properties) : {}; propertiesCopy["token"] = token; propertiesCopy["partnerId"] = apiKey; propertiesCopy["recorderId"] = recorderId; if (propertiesCopy.hasOwnProperty("style")) { var showControlBar = propertiesCopy.style.showControlBar; propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style)); } var params = {}; params.allowscriptaccess = "always"; if (propertiesCopy.wmode){ params.wmode = propertiesCopy.wmode; delete propertiesCopy["wmode"]; } else { params.wmode = "transparent"; } var attributes = {}; attributes.id = recorderId; attributes.style = "outline:none;"; if (!propertiesCopy.width || isNaN(propertiesCopy.width)) { propertiesCopy.width = DEFAULT_WIDTH; } if (!propertiesCopy.height || isNaN(propertiesCopy.height)) { propertiesCopy.height = DEFAULT_HEIGHT; if (showControlBar == false) { propertiesCopy.height -= CONTROL_BAR_HEIGHT; } } var createReplaceElement = false; if (!replaceElementId) { // Create a new element for the publisher and append it to the body replaceElementId = "recorder_replace_" + recorderCount; createReplaceElement = true; } swfobject.addDomLoadEvent(function() { if (createReplaceElement) { var div = document.createElement('div'); div.setAttribute('id', replaceElementId); document.body.appendChild(div); } embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_recordwidget.swf?partnerId="+apiKey, replaceElementId, propertiesCopy.width, propertiesCopy.height, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes); }); this.recorders[recorderId] = new Recorder(recorderId, replaceElementId, propertiesCopy); return this.recorders[recorderId]; }; this.removeRecorder = function(recorder) { if (!recorder) { var errorMsg = "Session.removeRecorder :: recorder cannot be null"; error(errorMsg); throw new Error(errorMsg); } debug("Session.removeRecorder(" + recorder.id + ")"); unloadComponent(recorder); delete this.recorders[recorder.id]; }; this.displayPlayer = function(archiveId, token, replaceElementId, properties) { if (!archiveId) { errorMsg = "RecorderManager.displayPlayer :: Valid ArchiveId required"; error(errorMsg); throw new Error(errorMsg); } var playerId = "player_" + apiKey + "_" + playerCount++; var propertiesCopy = (properties) ? copyObject(properties) : {}; propertiesCopy["token"] = token; propertiesCopy["archiveId"] = archiveId; propertiesCopy["partnerId"] = apiKey; propertiesCopy["playerId"] = playerId; if (propertiesCopy.hasOwnProperty("style")) { var showControlBar = propertiesCopy.style.showControlBar; propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style)); } var params = {}; params.allowscriptaccess = "always"; if (propertiesCopy.wmode){ params.wmode = propertiesCopy.wmode; delete propertiesCopy["wmode"]; } else { params.wmode = "transparent"; } var attributes = {}; attributes.id = playerId; attributes.style = "outline:none;"; if (!propertiesCopy.width || isNaN(propertiesCopy.width)) { propertiesCopy.width = DEFAULT_WIDTH; } if (!propertiesCopy.height || isNaN(propertiesCopy.height)) { propertiesCopy.height = DEFAULT_HEIGHT; if (showControlBar == false) { propertiesCopy.height -= CONTROL_BAR_HEIGHT; } } if (!propertiesCopy.autoPlay) { propertiesCopy.autoPlay = false; } var createReplaceElement = false; if (!replaceElementId) { // Create a new element for the player and append it to the body replaceElementId = "player_replace_" + playerCount; createReplaceElement = true; } swfobject.addDomLoadEvent(function() { if (createReplaceElement) { var div = document.createElement('div'); div.setAttribute('id', replaceElementId); document.body.appendChild(div); } embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_playerwidget.swf?partnerId="+apiKey, replaceElementId, propertiesCopy.width, propertiesCopy.height, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes); }); this.players[playerId] = new Player(playerId, replaceElementId, propertiesCopy); return this.players[playerId]; }; this.removePlayer = function(player) { if (!player) { var errorMsg = "Session.removePlayer :: player cannot be null"; error(errorMsg); throw new Error(errorMsg); } debug("Session.removePlayer(" + player.id + ")"); unloadComponent(player); delete this.players[player.id]; }; } function Session (sessionId) { this.superClass = EventDispatcher; this.superClass(); this.sessionId = sessionId; this.connection; this.subscribers = {}; this.publishers = {}; this.apiKey; this.capabilities; this.connected = false; this.connecting = false; var publisherCount = 1; var subscriberCount = 1; var DEFAULT_WIDTH = 264; var DEFAULT_HEIGHT = 198; var controllerId; var stateManager; this.connect = function(apiKey, token, properties) { if (this.connecting) { warn("Session.connect :: Patience, please."); return; } debug("Session.connect(" + apiKey + ")"); if (!TB.checkSystemRequirements()) { var errorMsg = "Session.connect :: Flash Player Version 10+ required"; error(errorMsg); throw new Error(errorMsg); } if (!apiKey) { errorMsg = "Session.connect :: API key required to connect"; error(errorMsg); throw new Error(errorMsg); } if (!token) { errorMsg = "Session.connect :: Token required to connect"; error(errorMsg); throw new Error(errorMsg); } if (this.connected) { warn("Session.connect :: Session already connected"); return; } this.connecting = true; var propertiesCopy = (properties) ? copyObject(properties) : {}; this.apiKey = apiKey; this.token = token; this.properties = properties; var params = {}; params.allowscriptaccess = "always"; if (propertiesCopy.wmode) { params.wmode = propertiesCopy.wmode; delete propertiesCopy["wmode"]; } if (propertiesCopy.connectionData) { propertiesCopy.connectionData = encodeURIComponent(propertiesCopy.connectionData); } controllerId = "controller_" + this.sessionId; var attributes = {}; attributes.id = controllerId; propertiesCopy["sessionId"] = this.sessionId; propertiesCopy["token"] = this.token; var replaceId = "replace_" + this.sessionId; swfobject.addDomLoadEvent(function() { var div = document.createElement('div'); div.setAttribute('id', replaceId); div.style.display = "none"; document.body.appendChild(div); var nowDate = new Date(); propertiesCopy["startTime"] = nowDate.getTime(); swfobject.embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_controllerwidget.swf?partnerId="+apiKey, replaceId, 1, 1, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes); }); if (window.location.protocol == "file:") { setTimeout("TB.controllerLoadCheck()", 8000); } }; this.disconnect = function() { debug("Session.disconnect()"); if (!controllerId || this.connecting) { warn("Session.disconnect :: No connection to disconnect"); return; } // Disconnect controller var controller = document.getElementById(controllerId); if (controller) { if (!isUnloading) { try { controller.cleanupView(); } catch(e) { var errorMsg = "Session.disconnect :: Failed to disconnect - " + e; error(errorMsg); throw new Error(errorMsg); } } } else { warn("Session.disconnect :: No connection to disconnect"); } }; this.disconnectComponents = function() { debug("Session.disconnectComponents() - disconnecting publishers and subscribers"); // As part of cleaning up connections, disconnect any publishers and subscribers for (var publisher in this.publishers) { if (this.publishers[publisher].hasOwnProperty("id")) disconnectComponent(this.publishers[publisher]); } for (var subscriber in this.subscribers) { if (this.subscribers[subscriber].hasOwnProperty("id")) disconnectComponent(this.subscribers[subscriber]); } }; this.cleanup = function() { debug("Session.cleanup()"); for (var publisher in this.publishers) { if (this.publishers[publisher].hasOwnProperty("id")) this.unpublish(this.publishers[publisher]); } for (var subscriber in this.subscribers) { if (this.subscribers[subscriber].hasOwnProperty("id")) this.unsubscribe(this.subscribers[subscriber]); } }; this.cleanupConnection = function() { // private function debug("Session.cleanupConnection() - removing controller"); this.connection = null; if (!controllerId) { warn("Session.cleanup :: No connection to clean up"); return; } if (document.getElementById(controllerId)) { setTimeout(function() { removeSWF(controllerId, "TB.sessionDisconnected :: "); controllerId = null; }, 0); // must be asynchronous } else { warn("Session.cleanup :: No connection to clean up"); } }; this.publish = function(replaceElementId, properties) { debug("Session.publish(" + replaceElementId + "):" + properties); if (!this.connection || !this.connection.connectionId) { var errorMsg = "Session.publish :: Connection required to publish"; error(errorMsg); throw new Error(errorMsg); } if (!replaceElementId) { // Create a new element for the publisher and append it to the body var div = document.createElement('div'); replaceElementId = "publisher_replace_" + this.sessionId + "_" + publisherCount; div.setAttribute('id', replaceElementId); document.body.appendChild(div); } // Check the name & data properties for length var propertiesCopy = (properties) ? copyObject(properties) : {}; if (propertiesCopy["name"] != undefined && propertiesCopy["name"].length > 1000) { errorMsg = "Session.publish :: name property longer than 1000 chars."; error(errorMsg); throw new Error(errorMsg); } if (propertiesCopy["data"] != undefined && propertiesCopy["data"].length > 1000) { errorMsg = "Session.publish :: data property longer than 1000 chars."; error(errorMsg); throw new Error(errorMsg); } var publisherId = "publisher_" + this.sessionId + "_" + publisherCount++; var publisher = new Publisher(publisherId, replaceElementId, propertiesCopy); return this._embedPublisher(publisher); }; // This function is not intended for publish use, it is so that we can republish when someone clicks // the deny button this._embedPublisher = function (publisher) { var replaceElement = document.getElementById(publisher.replacedDivId); if(!replaceElement) { errorMsg = "Session.publish :: replaceElementId does not exist in DOM."; error(errorMsg); throw new Error(errorMsg); } var params = {}; params.allowscriptaccess = "always"; params.cameraSelected = cameraSelected; if (publisher.properties.wmode){ params.wmode = publisher.properties.wmode; delete publisher.properties["wmode"]; } else { params.wmode = "transparent"; } if (publisher.properties.hasOwnProperty("style")) { publisher.properties.style = encodeURIComponent(JSONify(publisher.properties.style)); } var attributes = {}; attributes.id = publisher.id; attributes.style = "outline:none;"; publisher.properties["publisherId"] = publisher.id; publisher.properties["connectionId"] = this.connection.connectionId; publisher.properties["sessionId"] = this.sessionId; publisher.properties["token"] = this.token; publisher.properties["cameraSelected"] = cameraSelected; publisher.properties["simulateMobile"] = TB.simulateMobile; publisher.properties["publishCapability"] = this.capabilities.publish; if (!publisher.properties.width || isNaN(publisher.properties.width)) publisher.properties.width = DEFAULT_WIDTH; if (!publisher.properties.height || isNaN(publisher.properties.height)) publisher.properties.height = DEFAULT_HEIGHT; /** if (!publisher.properties.encodedWidth || isNaN(publisher.properties.encodedWidth)) publisher.properties.encodedWidth = DEFAULT_WIDTH; if (!publisher.properties.encodedHeight || isNaN(publisher.properties.encodedHeight)) publisher.properties.encodedHeight = DEFAULT_HEIGHT; */ this.publishers[publisher.id] = publisher; var nowDate = new Date(); publisher.properties["startTime"] = nowDate.getTime(); embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_publishwidget.swf?partnerId="+this.apiKey, publisher.replacedDivId, publisher.properties.width, publisher.properties.height, MIN_FLASH_VERSION, false, publisher.properties, params, attributes); return publisher; }; this.unpublish = function(publisher) { if (!publisher) { var errorMsg = "Session.unpublish :: publisher cannot be null"; error(errorMsg); throw new Error(errorMsg); } debug("Session.unpublish(" + publisher.id + ")"); if (publisher.panelId && deviceManager && deviceManager.panels[publisher.panelId]) { deviceManager.removePanel(deviceManager.panels[publisher.panelId]); } unloadComponent(publisher); delete this.publishers[publisher.id]; }; this.forceUnpublish = function(stream) { var streamId; if (stream && typeof(stream) == "string") { streamId = stream; } else if (stream && typeof(stream) == "object" && stream.hasOwnProperty("streamId")) { streamId = stream.streamId; } else { var errorMsg = "Session.forceUnpublish :: Invalid stream type"; error(errorMsg); throw new Error(errorMsg); } debug("Session.forceUnpublish(" + streamId + ")"); if (streamId) { try { var controller = document.getElementById(controllerId); controller.forceUnpublish(streamId); } catch(err) { errorMsg = "Session.forceUnpublish :: "+ err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.forceUnpublish :: Stream does not exist."; error(errorMsg); throw new Error(errorMsg); } }; this.subscribe = function(stream, replaceElementId, properties) { if (!this.connection || !this.connection.connectionId) { var errorMsg = "Session.subscribe :: Connection required to subscribe"; error(errorMsg); throw new Error(errorMsg); } if (!stream) { errorMsg = "Session.subscribe :: stream cannot be null"; error(errorMsg); throw new Error(errorMsg); } if (!stream.hasOwnProperty("streamId")) { errorMsg = "Session.subscribe :: invalid stream object"; error(errorMsg); throw new Error(errorMsg); } debug("Session.subscribe(" + stream.streamId + ")"); if (!replaceElementId) { // Create a new element for the subscriber and append it to the body var div = document.createElement('div'); replaceElementId = "subscriber_replace_" + this.sessionId + "_" + subscriberCount; div.setAttribute('id', replaceElementId); document.body.appendChild(div); } var replaceElement = document.getElementById(replaceElementId); if(!replaceElement) { errorMsg = "Session.subscribe :: replaceElementId does not exist in DOM."; error(errorMsg); throw new Error(errorMsg); } var propertiesCopy = (properties) ? copyObject(properties) : {}; if( stream && stream.hasOwnProperty("type") && stream.type == "multiplexed") { propertiesCopy.mixedStreamURI = "live-lowlatency"; } var subscriberId = "subscriber_" + stream.streamId + "_" + subscriberCount++; var subscriber = new Subscriber(stream, subscriberId, replaceElementId, propertiesCopy); var params = {}; params.allowscriptaccess = "always"; if (propertiesCopy.wmode){ params.wmode = propertiesCopy.wmode; delete propertiesCopy["wmode"]; } else { params.wmode = "transparent"; } if (propertiesCopy.hasOwnProperty("style")) { propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style)); } var attributes = {}; attributes.id = subscriber.id; attributes.style = "outline:none;"; propertiesCopy["subscriberId"] = subscriberId; propertiesCopy["connectionId"] = this.connection.connectionId; propertiesCopy["sessionId"] = this.sessionId; propertiesCopy["streamId"] = stream.streamId; propertiesCopy["streamType"] = stream.type; propertiesCopy["name"] = stream.name; propertiesCopy["token"] = this.token; propertiesCopy["simulateMobile"] = TB.simulateMobile; propertiesCopy["isPublishing"] = (Object.keys(this.publishers).length > 0); if(!stream.hasAudio) { propertiesCopy["subscribeToAudio"] = "false"; } if(!stream.hasVideo) { propertiesCopy["subscribeToVideo"] = "false"; } propertiesCopy["orientation"] = stream.orientation; propertiesCopy["peerId"] = stream.peerId; if (!propertiesCopy.width || isNaN(propertiesCopy.width)) propertiesCopy.width = DEFAULT_WIDTH; if (!propertiesCopy.height || isNaN(propertiesCopy.height)) propertiesCopy.height = DEFAULT_HEIGHT; this.subscribers[subscriber.id] = subscriber; var nowDate = new Date(); propertiesCopy["startTime"] = nowDate.getTime(); embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_subscribewidget.swf?partnerId="+this.apiKey, replaceElementId, propertiesCopy.width, propertiesCopy.height, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes); return subscriber; }; this.unsubscribe = function(subscriber) { if (!subscriber) { var errorMsg = "Subscribe.unsubscribe :: subscriber cannot be null"; error(errorMsg); throw new Error(errorMsg); } debug("Session.unsubscribe(" + subscriber.id + ")"); unloadComponent(subscriber); delete this.subscribers[subscriber.id]; }; this.signal = function() { debug("Session.signal()"); if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.sendSignal(); } catch(err) { var errorMsg = "Session.signal :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.signal :: Connection required to signal."; error(errorMsg); throw new Error(errorMsg); } }; this.forceDisconnect = function(connection) { if (connection) debug("Session.forceDisconnect(" + connection.connectionId + ")"); var connectionId; if (connection && typeof(connection) == "string") connectionId = connection; else if (connection && typeof(connection) == "object" && connection.hasOwnProperty("connectionId")) connectionId = connection.connectionId; else { var errorMsg = "Session.forceDisconnect :: Invalid connection type"; error(errorMsg); throw new Error(errorMsg); } if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.forceDisconnect(connectionId); } catch(err) { errorMsg = "Session.forceDisconnect :: "+ err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.forceDisconnect :: Connection required to forceDisconnect."; error(errorMsg); throw new Error(errorMsg); } }; this.getSubscribersForStream = function(stream) { var res = null; if (!stream) { var errorMsg = "Session.getSubscribersForStream :: stream cannot be null"; error(errorMsg); throw new Error(errorMsg); } else { var streamId; if (typeof(stream) == "string") { streamId = stream; } else if (typeof(stream) == "object" && stream.hasOwnProperty("streamId")) { streamId = stream.streamId; } else { errorMsg = "Session.getSubscribersForStream :: Invalid stream type"; error(errorMsg); throw new Error(errorMsg); } res = []; for (var sr in this.subscribers) { if (this.subscribers[sr].hasOwnProperty("stream") && this.subscribers[sr].stream.streamId == streamId) res.push(this.subscribers[sr]); } } return res; }; this.getPublisherForStream = function(stream) { if (!stream) { var errorMsg = "Session.getPublisherForStream :: stream cannot be null"; error(errorMsg); throw new Error(errorMsg); } else { var streamId; if (typeof(stream) == "string") { streamId = stream; } else if (typeof(stream) == "object" && stream.hasOwnProperty("streamId")) { streamId = stream.streamId; } else { errorMsg = "Session.getPublisherForStream :: Invalid stream type"; error(errorMsg); throw new Error(errorMsg); } for (var pub in this.publishers) { var publisher = document.getElementById(this.publishers[pub].id); if (publisher) { try { if (publisher.getStreamId() == streamId) return this.publishers[pub]; } catch (err) { warn("Failed to get streamId for publisher: " + this.publishers[pub].id); } } } } return null; }; this.createArchive = function(apiKey, type, title) { debug("Session.createArchive()"); if (controllerId && this.connection && this.connection.connectionId) { if (type == TB.PER_SESSION || type == TB.PER_STREAM) { try { var controller = document.getElementById(controllerId); controller.createArchive(apiKey, type, title); } catch(err) { errorMsg = "Session.createArchive :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.createArchive :: Invalid type specfied."; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.createArchive :: Connection required to create an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.loadArchive = function(archiveId) { debug("Session.loadArchive()"); if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.loadArchive(archiveId); } catch(err) { var errorMsg = "Session.loadArchive :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.loadArchive :: Connection required to load an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.startRecording = function(archive) { debug("Session.startRecording()"); archive = createdArchives[this.sessionId][archive.archiveId]; if (!archive) { var errorMsg = "Session.startRecording :: Archive not created."; error(errorMsg); throw new Error(errorMsg); } if (archive.type != TB.PER_SESSION) { errorMsg = "Session.startRecording :: Trying to record per session on a " + archive.type + " archive"; error(errorMsg); throw new Error(errorMsg); } if (archive.recording) { warn("Session.startRecording :: Trying to start recording when the archive is already recording"); return; } if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.startRecordingSession(archive.archiveId); archive.recording = true; } catch(err) { errorMsg = "Session.startRecording :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.startRecording :: Connection required to record an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.stopRecording = function(archive) { debug("Session.stopRecording()"); archive = createdArchives[this.sessionId][archive.archiveId]; if (!archive) { var errorMsg = "Session.stopRecording :: Archive not created."; error(errorMsg); throw new Error(errorMsg); } if (archive.type != TB.PER_SESSION) { errorMsg = "Session.stopRecording :: Trying to stop recording per session on a " + archive.type + " archive"; error(errorMsg); throw new Error(errorMsg); } if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.stopRecordingSession(archive.archiveId); archive.recording = false; } catch(err) { errorMsg = "Session.stopRecording :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.stopRecording :: Connection required to record an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.closeArchive = function(archive) { debug("Session.closeArchive()"); if (controllerId && this.connection && this.connection.connectionId) { try { var controller = document.getElementById(controllerId); controller.closeArchive(archive.archiveId); } catch(err) { var errorMsg = "Session.closeArchive :: " + err; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Session.closeArchive :: Connection required to close an archive."; error(errorMsg); throw new Error(errorMsg); } }; this.getStateManager = function() { debug("Session.getStateManager()"); if (stateManager) return stateManager; else if (controllerId && this.connection && this.connection.connectionId) { stateManager = new StateManager(controllerId); return stateManager; } var errorMsg = "Session.getStateManager :: Connection required to getState. Wait for sessionConnected before you getStateManager."; error(errorMsg); throw new Error(errorMsg); }; } function StateManager(controllerId, archiveId) { this.superClass = EventDispatcher; this.superClass(); var MAX_KEYS = 20; this.archiveId = archiveId; this.set = function(key, value) { var values = key; if (archiveId) { var errorMsg = "StateManager.set :: not allowed on StateManager objects for archives."; error(errorMsg); throw new Error(errorMsg); } if (typeof(key) == "string" && (typeof(value) == "string" || value == null)) { values = {}; values[key] = value; } else if (typeof(key) == "object" && value == null) { if (Object.keys(values).length > MAX_KEYS) { error("StateManager.set :: Maximum number of keys exceeded"); this.dispatchEvent(new ChangeFailedEvent("changeFailed", 405, "Maximum number of keys exceeded", values)); return; } } else { errorMsg = "StateManager.set :: Invalid parameters passed. set() takes either two string parameters or one object of key value pairs."; error(errorMsg); throw new Error(errorMsg); } for (var k in values) { if (typeof(values[k]) != "string" && values[k] != null) { error("StateManager.set :: Invalid value " + values[k].toString() + " is not a string"); this.dispatchEvent(new ChangeFailedEvent("changeFailed", 403, " Invalid value, value must be a string", values)); return; } }; if (controllerId) { try { var controller = document.getElementById(controllerId); controller.setState(values); } catch (err) { errorMsg = "StateManager.set :: " + err; error(errorMsg); throw new Error(errorMsg); } } }; this.superAddEventListener = this.addEventListener; this.addEventListener = function(type, listener) { var key = false; if (type == "changed") { key = null; } else if (type.indexOf("changed:") == 0) { // Tell the controller which keys we want to subscribe to key = type.split(":")[1]; } if (key !== false) { if (archiveId) { key = "TB_archive_" + archiveId + "_"; } // Tell the controller that we want to subscribe to all keys if (controllerId) { try { var controller = document.getElementById(controllerId); controller.subscribeToKeyChange(key); } catch(err) { var errorMsg = "StateManager.addEventListener :: " + err; error(errorMsg); throw new Error(errorMsg); } } } this.superAddEventListener(type, listener); }; // Need to figure out how to know whether there are any event listeners for a key // if there are none then we can stop listening on the shared object, otherwise we should // keep listening. // this.superRemoveEventListener = this.removeEventListener; // this.removeEventListener = function(type, listener) { // var key = false; // if (type == "changed") { // key = null; // } else if (type.indexOf("changed:") == 0) { // // Tell the controller which keys we want to subscribe to // key = type.split(":")[1]; // } // // if (key !== false) { // // Tell the controller that we want to subscribe to all keys // if (controllerId) { // try { // var controller = document.getElementById(controllerId); // controller.unsubscribeFromKeyChange(key); // } catch(err) { // var errorMsg = "StateManager.removeEventListener :: " + err; // error(errorMsg); // throw new Error(errorMsg); // } // } // } // // this.superRemoveEventListener(type, listener); // }; } //-------------------------------------- // PRIVATE HELPER FUNCTIONS //-------------------------------------- function setEchoSuppressionEnabled(sessionId, groupId, isEnabled) { try { var controller = document.getElementById("controller_" + sessionId); controller.setEchoSuppressionEnabled(groupId, isEnabled); } catch(err) { var errorMsg = "Group :: " + err; error(errorMsg); throw new Error(errorMsg); } } function getGroupProperties(sessionId, groupId) { var groupProperties = null; try { var controller = document.getElementById("controller_" + sessionId); var groupObject = controller.getGroupProperties(groupId); groupProperties = new GroupProperties(groupObject); } catch(err) { var errorMsg = "Group :: " + err; error(errorMsg); throw new Error(errorMsg); } return groupProperties; } function embedCallback (event) { if (!event.success) { error("Failed to embed SWF " + event.id); TB.exceptionHandler("Failed to embed SWF " + event.id, "Embed Failed", 2001); } } function embedSWF(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) { if (!swfobject.hasFlashPlayerVersion(swfVersionStr)) { error("Flash Player " + swfVersionStr + " or higher required"); TB.exceptionHandler("Flash Player " + swfVersionStr + " or higher required", "Embed Failed", 2001); return; } swfobject.embedSWF(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, embedCallback); } function createHandler(func, event) { return function() { if(func != null) { func(event); } else { error('Event handler is null'); } }; } function flashdebug (str) { window.opentokdebug.debug("[FLASHDEBUG] opentok: " + str); } function debug (str) { window.opentokdebug.debug("[DEBUG] opentok: " + str); } function info (str) { window.opentokdebug.info("[INFO] opentok: " + str); } function warn (str) { window.opentokdebug.warn("[WARN] opentok: " + str); } function error (str) { window.opentokdebug.error("[ERROR] opentok: " + str); } function traceOut (level, str) { var element = document.getElementById('opentok_console'); if (element) element.innerHTML += (str + '
'); } function getConnectionFromConnectionId (connectionId) { if (connectionMap.hasOwnProperty(connectionId)) { var connection = connectionMap[connectionId]; } else { connection = new Connection(connectionId, NaN, null); } return connection; } function getStream(streamObject, sessionId) { return new Stream(streamObject.streamId, getConnectionFromConnectionId(streamObject.connectionId), streamObject.name, streamObject.streamData, streamObject.type, streamObject.creationTime, streamObject.hasAudio, streamObject.hasVideo, streamObject.orientation, sessionId, streamObject.peerId, streamObject.quality); } function getStreams (streamObjects, sessionId) { var streams = []; for (var i=0; i < streamObjects.length; i++) { streams.push(getStream(streamObjects[i], sessionId)); } return streams; } function getArchive (archive, sessionId) { var newArchive = new Archive(archive.id, archive.type, archive.title, sessionId, archive.status); if (!createdArchives.hasOwnProperty(sessionId)) createdArchives[sessionId] = {}; createdArchives[sessionId][archive.id] = newArchive; return newArchive; } function getConnections (connectionObjects) { var connections = []; for (var i=0; i < connectionObjects.length; i++) { var connection = new Connection(connectionObjects[i].connectionId, connectionObjects[i].creationTime, connectionObjects[i].data); connections.push(connection); connectionMap[connection.connectionId] = connection; }; return connections; } function getGroups (sessionId,groupObjects) { var groups = []; for (var key in groupObjects) { if (groupObjects[key].hasOwnProperty("groupId")) groups.push(new Group(sessionId,groupObjects[key].groupId)); } return groups; } function getCamera (cameraObj) { if (cameraObj.status == TB.ACTIVE) { return new Camera(cameraObj.name, TB.ACTIVE); } else if (cameraObj.status == TB.INACTIVE) { return new Camera(cameraObj.name, TB.INACTIVE); } else { return new Camera(cameraObj.name, TB.UNKNOWN); } } function getMicrophone (microphoneObj) { return new Microphone(microphoneObj.name, microphoneObj.status); } function getCameras (cameraObjects) { var cameras = new Array(); for (var i=0; i < cameraObjects.length; i++) { cameras.push(new Camera(cameraObjects[i].name, cameraObjects[i].status)); }; return cameras; } function getMicrophones (microphoneObjects) { var microphones = new Array(); for (var i=0; i < microphoneObjects.length; i++) { microphones.push(new Microphone(microphoneObjects[i].name, microphoneObjects[i].status)); }; return microphones; } function disconnectComponent(component) { if(!component.hasOwnProperty("id")){ return; } var uicomponent = document.getElementById(component.id); if (uicomponent) { try { uicomponent.cleanupView(); } catch(e) { warn("Disconnecting " + component.id + " failed"); } } else { warn("Disconnecting " + component.id + " failed"); } } function unloadComponent (component) { var uicomponent = document.getElementById(component.id); if (uicomponent) { try { uicomponent.cleanupView(); var parentNode = uicomponent.parentNode; parentNode.removeChild(uicomponent); } catch(e) { warn("Removing " + component.id + " failed " + e); } } else { warn("Element " + component.id + " does not exist"); } } function removeSWF (componentId, message) { try { if (componentId) { swfobject.removeSWF(componentId); componentId = null; } } catch(err) { var errorMsg = message + err; error(errorMsg); TB.exceptionHandler(errorMsg, "Internal Error", 2000); } } function setStreamProperty (id, property, value) { var component = document.getElementById(id); if (component) { try { component.setStreamProperty(property, value); } catch (err) { var errorMsg = "Changing settings on component " + id + " failed."; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Component "+id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } } function setDevice (id, device, isCamera) { var component = document.getElementById(id); if (component) { try { if (isCamera) component.setCamera(device.name); else component.setMicrophone(device.name); } catch (err) { var errorMsg = "Changing hardware settings on publisher " + id + " failed."; error(errorMsg); throw new Error(errorMsg); } } else { errorMsg = "Publisher "+ id + " does not exist."; error(errorMsg); throw new Error(errorMsg); } } // Find highest Z-index - via StackOverflow function highZ(parent, limit){ limit = limit || Infinity; parent = parent || document.body; var who, temp, max= 1, A= [], i= 0; var children = parent.childNodes, length = children.length; while(i max && temp <= limit) max = temp; } return max; } // This function is only intended for highZ(). Other uses may be unpredictable. function deepCss(who, css) { var sty, val, dv= document.defaultView || window; if (who.nodeType == 1) { sty = css.replace(/\-([a-z])/g, function(a, b){ return b.toUpperCase(); }); val = who.style[sty]; if (!val) { if(who.currentStyle) val= who.currentStyle[sty]; else if (dv.getComputedStyle) { val= dv.getComputedStyle(who,"").getPropertyValue(css); } } } return val || ""; } function createHiddenElement (form, key, value) { var hiddenField = document.createElement("input"); hiddenField.setAttribute("name", key); hiddenField.setAttribute("value", value); hiddenField.setAttribute("type", "hidden"); form.appendChild(hiddenField); } function getDataForUIComponent (componentId) { try { var element = document.getElementById(componentId); if (element) { return element.fetchData(); } } catch (err) { warn("Failed to get logs for " + componentId + " " + err); return ""; } } function JSONify(object) { // JSONify the style property var styleString = "{ "; for (var key in object) { if (typeof(object[key]) == "boolean") styleString += '"' + key + '":' + object[key] + ', '; else styleString += '"' + key + '":"' + object[key].toString() + '", '; }; if (styleString.length > 1) { styleString = styleString.substring(0, styleString.length - 2) + " }"; } else { styleString = "{}"; } return styleString; } function copyObject(obj) { var newObj = (obj instanceof Array) ? [] : {}; for (var i in obj) { if (i == 'clone') continue; if (obj[i] && typeof obj[i] == "object") { newObj[i] = copyObject(obj[i]); } else newObj[i] = obj[i]; } return newObj; } //-------------------------------------- // EVENT HANDLERS //-------------------------------------- this.isUnloading = false; window.onunload = function() { isUnloading = true; for (var i in TB.sessions) { if (TB.sessions[i].hasOwnProperty("disconnect")) { // Stop sessionDisconnectedHandler from happening. Was causing crashes on Safari. // We are just doing all the cleanup now. TB.sessionDisconnectedHandler = function() {}; TB.sessions[i].disconnect(); TB.sessions[i].cleanupConnection(); TB.sessions[i].cleanup(); } } }; //-------------------------------------- // PRIVATE STATIC VARIABLES //-------------------------------------- var MIN_FLASH_VERSION = "10.0.0"; // Minimum width and height to fit the adobe settings UI var MIN_ADOBE_WIDTH = 215; var MIN_ADOBE_HEIGHT = 138; var deviceManager; var recorderManager; var deviceDetectorId; var cameraSelected = false; var showingIssueForm = false; var connectionMap = {}; var SUPPORT_SSL = "true"; var WIDGET_URL = "http://staging.tokbox.com"; if (SUPPORT_SSL == "true" && window.location.protocol == "https:") { WIDGET_URL = "https://staging.tokbox.com"; } var dispatcher = new EventDispatcher(); var createdArchives = {}; var loadedArchives = {}; var controllerLoaded = false; return { //-------------------------------------- // TB PUBLIC STATIC VARIABLES //-------------------------------------- sessions: {}, groups: {}, LOG: 5, DEBUG: 4, INFO: 3, WARN: 2, ERROR: 1, NONE: 0, // Activity Status for cams/mics ACTIVE: "active", INACTIVE: "inactive", UNKNOWN: "unknown", // Archive types PER_SESSION: "perSession", PER_STREAM: "perStream", // TB Events EXCEPTION: "exception", // Session Events SESSION_CONNECTED: "sessionConnected", SESSION_DISCONNECTED: "sessionDisconnected", STREAM_CREATED: "streamCreated", STREAM_DESTROYED: "streamDestroyed", CONNECTION_CREATED: "connectionCreated", CONNECTION_DESTROYED: "connectionDestroyed", SIGNAL_RECEIVED: "signalReceived", STREAM_PROPERTY_CHANGED: "streamPropertyChanged", MICROPHONE_LEVEL_CHANGED: "microphoneLevelChanged", ARCHIVE_CREATED: "archiveCreated", ARCHIVE_CLOSED: "archiveClosed", ARCHIVE_LOADED: "archiveLoaded", ARCHIVE_SAVED: "archiveSaved", SESSION_RECORDING_STARTED: "sessionRecordingStarted", SESSION_RECORDING_STOPPED: "sessionRecordingStopped", SESSION_RECORDING_IN_PROGRESS: "sessionRecordingInProgress", STREAM_RECORDING_IN_PROGRESS: "streamRecordingInProgress", SESSION_NOT_RECORDING: "sessionNotRecording", STREAM_RECORDING_STARTED: "streamRecordingStarted", STREAM_RECORDING_STOPPED: "streamRecordingStopped", PLAYBACK_STARTED: "playbackStarted", PLAYBACK_STOPPED: "playbackStopped", RECORDING_STARTED: "recordingStarted", RECORDING_STOPPED: "recordingStopped", // Group Events GROUP_PROPERTIES_UPDATED: "groupPropertiesUpdated", // Publisher Events RESIZE: "resize", SETTINGS_BUTTON_CLICK: "settingsButtonClick", DEVICE_INACTIVE: "deviceInactive", ACCESS_ALLOWED: "accessAllowed", ACCESS_DENIED: "accessDenied", ECHO_CANCELLATION_MODE_CHANGED: "echoCancellationModeChanged", // DeviceManager Events DEVICES_DETECTED: "devicesDetected", // DevicePanel Events DEVICES_SELECTED: "devicesSelected", CLOSE_BUTTON_CLICK: "closeButtonClick", HAS_REQUIREMENTS: 1, OLD_FLASH_VERSION: 0, // Stream types BASIC_STREAM: "basic", MULTIPLEXED_STREAM: "multiplexed", ARCHIVED: "archive", // Global group ID GLOBAL_GROUP: "global", // Multiplexer Switch Type MULTIPLEXER_TIMEOUT_BASED_SWITCH: 0, MULTIPLEXER_ACTIVITY_BASED_SWITCH: 1, simulateMobile: false, //-------------------------------------- // TB STATIC FUNCTIONS //-------------------------------------- setLogLevel: function(value) { window.opentokdebug.setLevel(value); if (value == this.NONE) window.opentokdebug.setCallback(null); else window.opentokdebug.setCallback(traceOut, true); debug("TB.setLogLevel(" + value + ")" ); }, log: function(str) { window.opentokdebug.log("[LOG] opentok: " + str); }, initSession: function(sessionId) { debug("TB.initSession(" + sessionId + ")"); if (sessionId == null || sessionId == "") { var errorMsg = "TB.initSession :: sessionId cannot be null"; error(errorMsg); throw new Error(errorMsg); } if (!this.sessions.hasOwnProperty(sessionId)) { this.sessions[sessionId] = new Session(sessionId); } return this.sessions[sessionId]; }, initDeviceManager: function(apiKey) { debug("TB.initDeviceManager(" + apiKey + ")"); if (!apiKey) { var errorMsg = "TB.initDeviceManager :: apiKey cannot be null"; error(errorMsg); throw new Error(errorMsg); } if (!deviceManager) { deviceManager = new DeviceManager(apiKey); } return deviceManager; }, initRecorderManager: function(apiKey) { debug("TB.initRecorderManager(" + apiKey + ")"); if (!apiKey) { var errorMsg = "TB.initRecorderManager :: apiKey cannot be null"; error(errorMsg); throw new Error(errorMsg); } if (!recorderManager) { recorderManager = new RecorderManager(apiKey); } return recorderManager; }, addEventListener: function(type, callback) { debug("TB.addEventListener(" + type + ")"); dispatcher.addEventListener(type, callback); }, removeEventListener: function(type, callback) { debug("TB.removeEventListener(" + type + ")"); dispatcher.removeEventListener(type, callback); }, dispatchEvent: function(event) { debug("TB.dispatchEvent()"); event.target = this; dispatcher.dispatchEvent(event); }, checkSystemRequirements: function() { debug("TB.checkSystemRequirements()"); return swfobject.hasFlashPlayerVersion(MIN_FLASH_VERSION) ? this.HAS_REQUIREMENTS : this.OLD_FLASH_VERSION; }, //-------------------------------------- // FLASH CALLBACK HANDLERS //-------------------------------------- // TB callbacks exceptionHandler: function(msg, title, errorCode) { error("TB.exception :: title: " + title + " msg: " + msg + " errorCode: " + errorCode); try { this.dispatchEvent(new ExceptionEvent(this.EXCEPTION, msg, title, errorCode)); } catch(err) { var errorMsg = "TB.exception :: Failed to dispatch exception - " + err; error(errorMsg); // Don't throw an error because this is asynchronous // don't do an exceptionHandler because that would be recursive } }, // private callback controllerLoadedHandler: function() { controllerLoaded = true; }, controllerLoadCheck: function(event) { if (!controllerLoaded) { var confirmMsg = "The connection timed out. Make sure that you have allowed this page in the" + "Flash Player Global Settings Manager. Go to:"; adobeURL = "http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html"; prompt(confirmMsg, adobeURL); } }, // private callback flashLogger: function(msg) { flashdebug(msg); }, // private callback destroyStreamHandler: function(sessionId, streamObjects) { debug("TB.destroyStream"); try { var session = this.sessions[sessionId]; var streams = getStreams(streamObjects, sessionId); var action = function() { for (var i = 0; i < streams.length; i++) { var publisher = session.getPublisherForStream(streams[i]); if (publisher) { session.unpublish(publisher); } } }; // The event handler is called asynchronously after 2 milliseconds. setTimeout(action, 2); } catch(err) { var errorMsg = "TB.destroyStream :: " + err; error(errorMsg); TB.exceptionHandler(errorMsg, "Internal Error", 2000); } }, // Session callbacks sessionConnectedHandler: function(sessionId, connectionId, connectionObjects, streamObjects, groupObjects, capabilities, connectionQuality, p_archives) { debug("TB.sessionConnected"); try { var session = this.sessions[sessionId]; for(var i=0, len = connectionObjects.length; i < len; i++) { connection = connectionObjects[i]; if(connection.connectionId == connectionId) { session.connection = new Connection(connectionId, connection.creationTime, connection.data); break; } } session.connected = true; session.connecting = false; session.connection.quality = connectionQuality; session.capabilities = capabilities; var connections = getConnections(connectionObjects); var streams = getStreams(streamObjects, session.sessionId); var groups = getGroups(sessionId,groupObjects); for (var i=0; i < groups.length; i++) { this.groups[sessionId + "_" + groups[i].groupId] = groups[i]; } var archives = []; for (var i=0; i < p_archives.length; i++) { var newArchive = getArchive(p_archives[i], sessionId); archives.push(newArchive); }; session.dispatchEvent(new SessionConnectEvent(this.SESSION_CONNECTED, connections, streams, groups, archives)); } catch(err) { var errorMsg = "TB.sessionConnected :: "+err; error(errorMsg); TB.exceptionHandler(errorMsg, "Internal Error", 2000); } }, sessionDisconnectedHandler: function(sessionId, reason) { debug("TB.sessionDisconnected(" + reason + ")"); try { var session = this.sessions[sessionId]; session.disconnectComponents(); session.cleanupConnection(); session.connected = false; var event = new SessionDisconnectEvent(this.SESSION_DISCONNECTED, reason, true); session.dispatchEvent(event); var defaultAction = function() { if (!event.isDefaultPrevented()) { session.cleanup(); } }; // The event handler is called asynchronously after 1 millisecond. The default action happens after that. setTimeout(defaultAction, 2); } catch(err) { var errorMsg = "TB.sessionDisconnected :: " + err; error(errorMsg); TB.exceptionHandler(errorMsg, "Internal Error", 2000); } }, streamCreatedHandler: function(sessionId, streamObjects, reason) { debug("TB.streamCreated"); try { var session = this.sessions[sessionId]; var streams = getStreams(streamObjects, sessionId); session.dispatchEvent(new StreamEvent(this.STREAM_CREATED, streams, reason)); //notify publisher if there's an archive in flight var myArchives = createdArchives[sessionId]; for (var bob in myArchives) { for (var i=0; iWe're sorry to hear that something went wrong.

Please help us to debug your issue by providing a description of what happened."; div.style.backgroundColor = "#F7F7F7"; div.style.border = "1px solid #CCC"; div.style.fontWeight = "normal"; div.style.fontFamily = "'Lucida Grande', 'Trebuchet MS', sans-serif"; div.style.color = "#4c4c4c"; div.style.fontSize = "13px"; div.appendChild(form); document.body.appendChild(div); showingIssueForm = true; closeForm = function() { document.body.removeChild(div); showingIssueForm = false; }; cancelBtn.onclick = closeForm; } form.onsubmit = function() { window.open('#', 'formresult', 'scrollbars=no,menubar=no,height=200,width=400,resizable=yes,toolbar=no,status=no'); setTimeout(function() {closeForm();}, 1000); }; if (showReport) { createHiddenElement(form, "showReport", true); document.body.appendChild(form); form.submit(); } } }; }(); /*! * SWFObject v2.2 * is released under the MIT License * * 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 */ var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab. window.opentokdebug = (function(){ var window = this, // Some convenient shortcuts. aps = Array.prototype.slice, con = window.console, // Public object to be returned. that = {}, callback_func, callback_force, // OpenTok has a default of no logging. log_level = 0, // Logging methods, in "priority order". Not all console implementations // will utilize these, but they will be used in the callback passed to // setCallback. log_methods = ['error', 'warn', 'info', 'debug', 'log'], // Pass these methods through to the console if they exist, otherwise just // fail gracefully. These methods are provided for convenience. pass_methods = 'assert clear count dir dirxml exception group groupCollapsed groupEnd profile profileEnd table time timeEnd trace'.split(' '), idx = pass_methods.length, // Logs are stored here so that they can be recalled as necessary. logs = []; while (--idx >= 0) { (function(method) { // Generate pass-through methods. These methods will be called, if they // exist, as long as the logging level is non-zero. that[method] = function() { log_level !== 0 && con && con[method] && con[method].apply(con, arguments); }; })(pass_methods[idx]); } idx = log_methods.length; while (--idx >= 0) { (function(idx, level) { // Method: debug.log // // Call the console.log method if available. Adds an entry into the logs // array for a callback specified via . // // Usage: // // debug.log( object [, object, ...] ); - - // // Arguments: // // object - (Object) Any valid JavaScript object. // Method: debug.debug // // Call the console.debug method if available, otherwise call console.log. // Adds an entry into the logs array for a callback specified via // . // // Usage: // // debug.debug( object [, object, ...] ); - - // // Arguments: // // object - (Object) Any valid JavaScript object. // Method: debug.info // // Call the console.info method if available, otherwise call console.log. // Adds an entry into the logs array for a callback specified via // . // // Usage: // // debug.info( object [, object, ...] ); - - // // Arguments: // // object - (Object) Any valid JavaScript object. // Method: debug.warn // // Call the console.warn method if available, otherwise call console.log. // Adds an entry into the logs array for a callback specified via // . // // Usage: // // debug.warn( object [, object, ...] ); - - // // Arguments: // // object - (Object) Any valid JavaScript object. // Method: debug.error // // Call the console.error method if available, otherwise call console.log. // Adds an entry into the logs array for a callback specified via // . // // Usage: // // debug.error( object [, object, ...] ); - - // // Arguments: // // object - (Object) Any valid JavaScript object. that[level] = function() { var args = aps.call(arguments), log_arr = [level].concat(args); logs.push(log_arr); if (!con || !is_level(idx)) { return; } exec_callback(log_arr); // OpenTok executes callback only if the proper level // OpenTok - this is a fix for firebug 1.6.0 submitted by someone else. hopefully it'll get incorporated into // the next official release and it can be removed. (con.firebug || window.Firebug) ? con[level].apply(con, args) : con[level] ? con[level](args) : con.log(args); }; })(idx, log_methods[idx]); } // Execute the callback function if set. function exec_callback(args) { if (callback_func && (callback_force || !con || !con.log)) { callback_func.apply(window, args); } }; // Method: debug.setLevel // // Set a minimum or maximum logging level for the console. Doesn't affect // the callback function, but if set to 0 to disable // logging, will be disabled as well. // // Usage: // // debug.setLevel( [ level ] ) - - // // Arguments: // // level - (Number) If 0, disables logging. If negative, shows N lowest // priority levels of log messages. If positive, shows N highest priority // levels of log messages. // // Priority levels: // // log (1) < debug (2) < info (3) < warn (4) < error (5) that.setLevel = function(level) { log_level = typeof level === 'number' ? level : 9; }; // Determine if the level is visible given the current log_level. function is_level(level) { return log_level > 0 ? log_level > level : log_methods.length + log_level <= level; }; // Method: debug.setCallback // // Set a callback to be used if logging isn't possible due to console.log // not existing. If unlogged logs exist when callback is set, they will all // be logged immediately unless a limit is specified. // // Usage: // // debug.setCallback( callback [, force ] [, limit ] ) // // Arguments: // // callback - (Function) The aforementioned callback function. The first // argument is the logging level, and all subsequent arguments are those // passed to the initial debug logging method. // force - (Boolean) If false, log to console.log if available, otherwise // callback. If true, log to both console.log and callback. // limit - (Number) If specified, number of lines to limit initial scrollback // to. that.setCallback = function() { var args = aps.call(arguments), max = logs.length, i = max; callback_func = args.shift() || null; callback_force = typeof args[0] === 'boolean' ? args.shift() : false; i -= typeof args[0] === 'number' ? args.shift() : max; while (i < max) { exec_callback(logs[i++]); } }; that.getLogs = function() { return logs.join('\n'); }; return that; })();// Add missing IE methods // This was taken from: // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys if (!Object.keys) Object.keys = function(o) { if (o !== Object(o)) throw new TypeError('Object.keys called on non-object'); var ret = [], p; for (p in o) { if (Object.prototype.hasOwnProperty.call(o, p)) { ret.push(p); } } return ret; }; // This was taken from: // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(searchElement) { "use strict"; if (this === void 0 || this === null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 0) { n = Number(arguments[1]); if (n !== n) { // shortcut for verifying if it's NaN n = 0; } else if (n !== 0 && n !== Infinity && n !== -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; }; }