social_stream-presence 0.7.5 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/opentok.rb ADDED
@@ -0,0 +1,29 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ =begin
5
+ OpenTok Ruby Library
6
+ http://www.tokbox.com/
7
+
8
+ Copyright 2010, TokBox, Inc.
9
+
10
+ Last modified: 2011-02-17
11
+ =end
12
+
13
+
14
+ require 'rubygems'
15
+ require 'net/http'
16
+ require 'uri'
17
+ require 'digest/md5'
18
+ require 'cgi'
19
+ #require 'pp' # just for debugging purposes
20
+
21
+ Net::HTTP.version_1_2 # to make sure version 1.2 is used
22
+
23
+ module OpenTok
24
+ VERSION = "tbrb-v0.91.2011-02-17"
25
+ API_URL = "https://api.opentok.com/hl";
26
+ end
27
+
28
+ require 'OpenTok/Exceptions'
29
+ require 'OpenTok/OpenTokSDK'
@@ -33,12 +33,16 @@ module SocialStream
33
33
 
34
34
  mattr_accessor :social_stream_presence_username
35
35
  mattr_accessor :password
36
+
37
+ mattr_accessor :opentok_api_key
38
+ mattr_accessor :opentok_api_secret
36
39
 
37
40
  @@auth_method = "cookie"
38
41
  @@remote_xmpp_server = false
39
42
  @@secure_rest_api = false
40
43
  @@enable = false
41
44
  @@social_stream_presence_username = "social_stream_presence"
45
+ @@opentok_api_key = "default"
42
46
 
43
47
  class << self
44
48
  def setup
@@ -1,5 +1,5 @@
1
1
  module SocialStream
2
2
  module Presence
3
- VERSION = "0.7.5"
3
+ VERSION = "0.8.0"
4
4
  end
5
5
  end
@@ -0,0 +1,4329 @@
1
+ /*!
2
+ * OpenTok JavaScript Library v0.91.46
3
+ * http://www.tokbox.com/
4
+ *
5
+ * Copyright (c) 2011 TokBox, Inc.
6
+ *
7
+ * Date: February 16 18:15:10 2012
8
+ */
9
+
10
+ function getHostname() {
11
+ return window.location.hostname;
12
+ }
13
+
14
+ // Instrumentation
15
+
16
+ TB = function() {
17
+
18
+ //--------------------------------------
19
+ // EVENT CLASSES
20
+ //--------------------------------------
21
+
22
+ function EventDispatcher() {
23
+ this._listeners = {};
24
+
25
+ this.addEventListener = function(type, listener) {
26
+ if (!type) {
27
+ throw new Error("EventDispatcher.addEventListener :: No type specified");
28
+ }
29
+ if (!listener) {
30
+ throw new Error("EventDispatcher.addEventListener :: No listener function specified");
31
+ }
32
+
33
+
34
+ if (!this._listeners.hasOwnProperty(type)) {
35
+ this._listeners[type] = new Array();
36
+ }
37
+ this.removeEventListener(type, listener); // You cannot have the same listener for the same type multiple times
38
+ debug("TB.addEventListener(" + type + ")");
39
+ this._listeners[type].push(listener);
40
+ };
41
+
42
+ this.removeEventListener = function(type, listener) {
43
+ if (!type) {
44
+ throw new Error("EventDispatcher.removeEventListener :: No type specified");
45
+ }
46
+ if (!listener) {
47
+ throw new Error("EventDispatcher.removeEventListener :: No listener function specified");
48
+ }
49
+
50
+ debug("TB.removeEventListener(" + type + ")");
51
+ if (this._listeners.hasOwnProperty(type)) {
52
+ for (var i=0; i < this._listeners[type].length; i++) {
53
+ if (this._listeners[type][i] == listener) {
54
+ this._listeners[type].splice(i, 1);
55
+ break;
56
+ }
57
+ };
58
+ }
59
+ };
60
+
61
+ this.dispatchEvent = function(event) {
62
+ if (!event) {
63
+ throw new Error("EventDispatcher.dispatchEvent :: No event specified");
64
+ }
65
+ if (!event.type) {
66
+ throw new Error("EventDispatcher.dispatchEvent :: Event has no type");
67
+ }
68
+ if (!event.target) {
69
+ event.target = this;
70
+ }
71
+
72
+ if (this._listeners.hasOwnProperty(event.type)) {
73
+ var listeners = this._listeners[event.type];
74
+
75
+ if (listeners instanceof Array) {
76
+ for (var i=0; i < listeners.length; i++) {
77
+ var handler = createHandler(listeners[i], event);
78
+ // We run this asynchronously so that it doesn't interfere with execution if an error happens
79
+ // eg. multiple event handlers are added one has an error so the subsequent ones fail
80
+ setTimeout(handler, 1);
81
+ };
82
+ } else {
83
+ throw new Error("EventDispatcher.dispatchEvent :: Invalid object type in listeners");
84
+ }
85
+ }
86
+ };
87
+ }
88
+
89
+ function Event (type, cancelable) {
90
+ this.type = type;
91
+ this.cancelable = cancelable ? cancelable : false;
92
+ this.target;
93
+
94
+ var defaultPrevented = false;
95
+
96
+ this.preventDefault = function() {
97
+ if (this.cancelable) {
98
+ defaultPrevented = true;
99
+ } else {
100
+ warn("Event.preventDefault :: Trying to preventDefault on an Event that isn't cancelable");
101
+ }
102
+ };
103
+
104
+ this.isDefaultPrevented = function() {
105
+ return defaultPrevented;
106
+ };
107
+ }
108
+
109
+ function ExceptionEvent (type, message, title, code) {
110
+ this.superClass = Event;
111
+ this.superClass(type);
112
+
113
+ this.message = message;
114
+ this.title = title;
115
+ this.code = code;
116
+ }
117
+
118
+ function ConnectionEvent (type, connections, reason) {
119
+ this.superClass = Event;
120
+ this.superClass(type);
121
+
122
+ this.connections = connections;
123
+ this.reason = reason;
124
+ }
125
+
126
+ function StreamEvent (type, streams, reason, cancelable) {
127
+ this.superClass = Event;
128
+ this.superClass(type, cancelable);
129
+
130
+ this.streams = streams;
131
+ this.reason = reason;
132
+ }
133
+
134
+ function SessionConnectEvent (type, connections, streams, groups, archives) {
135
+ this.superClass = Event;
136
+ this.superClass(type);
137
+
138
+ this.connections = connections;
139
+ this.streams = streams;
140
+ this.groups = groups;
141
+ this.archives = archives;
142
+ }
143
+
144
+ function SessionDisconnectEvent (type, reason, cancelable) {
145
+ this.superClass = Event;
146
+ this.superClass(type, cancelable);
147
+
148
+ this.reason = reason;
149
+ }
150
+
151
+ function SignalEvent (type, fromConnection) {
152
+ this.superClass = Event;
153
+ this.superClass(type);
154
+
155
+ this.fromConnection = fromConnection;
156
+ }
157
+
158
+ function VolumeEvent(type, streamId, volume) {
159
+ this.superClass = Event;
160
+ this.superClass(type);
161
+
162
+ this.streamId = streamId;
163
+ this.volume = volume;
164
+ }
165
+
166
+
167
+ function DeviceEvent (type, camera, microphone) {
168
+ this.superClass = Event;
169
+ this.superClass(type);
170
+
171
+ this.camera = camera;
172
+ this.microphone = microphone;
173
+ }
174
+
175
+ function GroupEvent (type, group, reason) {
176
+ this.superClass = Event;
177
+ this.superClass(type);
178
+
179
+ this.group = group;
180
+ this.reason = reason;
181
+ }
182
+
183
+ function DeviceStatusEvent (type, cameras, microphones, selectedCamera, selectedMicrophone) {
184
+ this.superClass = Event;
185
+ this.superClass(type);
186
+
187
+ this.cameras = cameras;
188
+ this.microphones = microphones;
189
+ this.selectedCamera = selectedCamera;
190
+ this.selectedMicrophone = selectedMicrophone;
191
+ }
192
+
193
+ function ResizeEvent (type, widthFrom, widthTo, heightFrom, heightTo) {
194
+ this.superClass = Event;
195
+ this.superClass(type);
196
+
197
+ this.widthFrom = widthFrom;
198
+ this.widthTo = widthTo;
199
+ this.heightFrom = heightFrom;
200
+ this.heightTo = heightTo;
201
+ }
202
+
203
+ function StreamPropertyChangedEvent(type, stream, changedProperty, oldValue, newValue) {
204
+ this.superClass = Event;
205
+ this.superClass(type);
206
+
207
+ this.type = type;
208
+ this.stream = stream;
209
+ this.changedProperty = changedProperty;
210
+ this.oldValue = oldValue;
211
+ this.newValue = newValue;
212
+ }
213
+
214
+ function ArchiveEvent (type, archives) {
215
+ this.superClass = Event;
216
+ this.superClass(type);
217
+
218
+ this.archives = archives;
219
+ }
220
+
221
+ function ArchiveStreamEvent (type, archive, streams) {
222
+ this.superClass = Event;
223
+ this.superClass(type);
224
+
225
+ this.archive = archive;
226
+ this.streams = streams;
227
+ }
228
+
229
+ function StateChangedEvent(type, changedValues) {
230
+ this.superClass = Event;
231
+ this.superClass(type);
232
+ this.changedValues = changedValues;
233
+ }
234
+
235
+ function ChangeFailedEvent(type, reasonCode, reason, failedValues) {
236
+ this.superClass = Event;
237
+ this.superClass(type);
238
+
239
+ this.reasonCode = reasonCode;
240
+ this.reason = reason;
241
+ this.failedValues = failedValues;
242
+ }
243
+
244
+ //--------------------------------------
245
+ // CLASSES
246
+ //--------------------------------------
247
+
248
+ function Connection (connectionId, creationTime, data) {
249
+ this.connectionId = connectionId;
250
+ this.creationTime = Number(creationTime);
251
+ this.data = data;
252
+
253
+ this.quality;
254
+ }
255
+
256
+
257
+ function Stream (streamId, connection, name, data, type, creationTime, hasAudio, hasVideo, orientation, sessionId, peerId, quality) {
258
+ //INSTANCE VARIABLES
259
+ this.streamId = streamId;
260
+ this.connection = connection;
261
+ this.name = name;
262
+ this.data = data;
263
+ this.type = type;
264
+ this.creationTime = creationTime;
265
+ this.hasAudio = hasAudio;
266
+ this.hasVideo = hasVideo;
267
+ this.orientation = orientation;
268
+ this.peerId = peerId;
269
+ this.quality = quality;
270
+
271
+ this.startRecording = function(archive) {
272
+ debug("Stream.startRecording()");
273
+ var controllerId = "controller_" + sessionId;
274
+ archive = createdArchives[sessionId][archive.archiveId];
275
+ if (!archive) {
276
+ var errorMsg = "Stream.startRecording :: Archive not created.";
277
+ error(errorMsg);
278
+ throw new Error(errorMsg);
279
+ }
280
+ if (archive.type != TB.PER_STREAM) {
281
+ errorMsg = "Stream.startRecording :: Trying to record per stream on a " + archive.type + " archive";
282
+ error(errorMsg);
283
+ throw new Error(errorMsg);
284
+ }
285
+ if (controllerId && this.connection && this.connection.connectionId) {
286
+ try {
287
+ var controller = document.getElementById(controllerId);
288
+ controller.startRecordingStream(this.streamId, archive.archiveId);
289
+ archive.recording = true;
290
+ } catch(err) {
291
+ errorMsg = "Stream.startRecording :: " + err;
292
+ error(errorMsg);
293
+ throw new Error(errorMsg);
294
+ }
295
+ } else {
296
+ errorMsg = "Stream.startRecording :: Connection required to record an archive.";
297
+ error(errorMsg);
298
+ throw new Error(errorMsg);
299
+ }
300
+ };
301
+
302
+ this.stopRecording = function(archive) {
303
+ debug("Stream.stopRecording()");
304
+ archive = createdArchives[sessionId][archive.archiveId];
305
+ if (!archive) {
306
+ var errorMsg = "Stream.stopRecording :: Archive not created.";
307
+ error(errorMsg);
308
+ throw new Error(errorMsg);
309
+ }
310
+ if (archive.type != TB.PER_STREAM) {
311
+ errorMsg = "Stream.stopRecording :: Trying to stop recording per stream on a " + archive.type + " archive";
312
+ error(errorMsg);
313
+ throw new Error(errorMsg);
314
+ }
315
+ var controllerId = "controller_" + sessionId;
316
+ if (controllerId && this.connection && this.connection.connectionId) {
317
+ try {
318
+ var controller = document.getElementById(controllerId);
319
+ controller.stopRecordingStream(this.streamId, archive.archiveId);
320
+ } catch(err) {
321
+ errorMsg = "Stream.stopRecording :: " + err;
322
+ error(errorMsg);
323
+ throw new Error(errorMsg);
324
+ }
325
+ } else {
326
+ errorMsg = "Stream.stopRecording :: Connection required to record an archive.";
327
+ error(errorMsg);
328
+ throw new Error(errorMsg);
329
+ }
330
+ };
331
+ }
332
+
333
+ function UIComponent (id, replacedDivId) {
334
+ this.id = id;
335
+ this.replacedDivId = replacedDivId;
336
+ this.parentClass = EventDispatcher;
337
+ this.parentClass();
338
+ }
339
+
340
+ function StylableComponent(id, replacedDivId) {
341
+ this.uberClass = UIComponent;
342
+ this.uberClass(id, replacedDivId);
343
+
344
+ var componentStyles = ["showMicButton", "showSpeakerButton", "showSettingsButton", "showCameraToggleButton", "nameDisplayMode", "buttonDisplayMode", "showSaveButton", "showRecordButton", "showRecordStopButton", "showReRecordButton", "showPauseButton", "showPlayButton", "showPlayStopButton", "showStopButton", "backgroundImageURI", "showControlPanel", "showRecordCounter", "showPlayCounter", "showControlBar"];
345
+ this.getStyle = function(key) {
346
+ var component = document.getElementById(this.id);
347
+ if (!this.loaded) {
348
+ if (key) {
349
+ return this._style[key];
350
+ } else {
351
+ return this._style;
352
+ }
353
+ } else if (component) {
354
+ try {
355
+ var style = component.getStyle(key);
356
+ if (typeof(style) == "string")
357
+ return style;
358
+ for (var i in style) {
359
+ if (style[i] == "false")
360
+ style[i] = false;
361
+ if (style[i] == "true")
362
+ style[i] = true;
363
+ if (componentStyles.indexOf(i) < 0) {
364
+ // Strip unnecessary properties out
365
+ delete style[i];
366
+ }
367
+ };
368
+ return style;
369
+ } catch (err) {
370
+ var errorMsg = "Publisher.getStyle:: Failed to call getStyle. " + err;
371
+ error(errorMsg);
372
+ throw new Error(errorMsg);
373
+ }
374
+ } else {
375
+ errorMsg = "Publisher.getStyle:: Publisher " + this.id + " does not exist.";
376
+ error(errorMsg);
377
+ throw new Error(errorMsg);
378
+ }
379
+ };
380
+
381
+ this._style = {};
382
+ var validStyleValues = {
383
+ buttonDisplayMode: ["auto", "off", "on"],
384
+ nameDisplayMode: ["auto", "off", "on"],
385
+ showSettingsButton: [true, false],
386
+ showMicButton: [true, false],
387
+ showCameraToggleButton: [true, false],
388
+ showSaveButton: [true, false],
389
+ backgroundImageURI: null,
390
+ showControlBar: [true, false],
391
+ showPlayCounter: [true, false],
392
+ showRecordCounter: [true, false]
393
+ };
394
+ this.setStyle = function(key, value) {
395
+ debug("Publisher.setStyle: " + key.toString());
396
+ var component = document.getElementById(this.id);
397
+ if (!this.loaded) {
398
+ if ((typeof(key) == "string") && value != null) {
399
+ if (this._style.hasOwnProperty(key) && (key == "backgroundImageURI" || (validStyleValues[key].indexOf(value) > -1)) ) {
400
+ debug("setStyle::Setting " + key + " to " + value);
401
+ this._style[key] = value;
402
+ } else {
403
+ warn("setStyle::Invalid style property passed " + key + " : " + value);
404
+ }
405
+ } else {
406
+ for (var i in key) {
407
+ this.setStyle(i, key[i]);
408
+ };
409
+ }
410
+ this.modified = true;
411
+ } else if (component) {
412
+ try {
413
+ component.setStyle(key, value);
414
+ } catch (err) {
415
+ var errorMsg = "Publisher.setStyle:: Failed to call setStyle. " + err;
416
+ error(errorMsg);
417
+ throw new Error(errorMsg);
418
+ }
419
+ } else {
420
+ errorMsg = "Publisher.setStyle:: Publisher " + this.id + " does not exist.";
421
+ error(errorMsg);
422
+ throw new Error(errorMsg);
423
+ }
424
+
425
+ return this;
426
+ };
427
+ }
428
+
429
+ function VideoComponent(id, replacedDivId) {
430
+ this.supClass = StylableComponent;
431
+ this.supClass(id, replacedDivId);
432
+
433
+ this.getImgData = function() {
434
+ debug("VideoComponent.getImgData");
435
+
436
+ var component = document.getElementById(this.id);
437
+ if (component) {
438
+ try {
439
+ return component.getImgData();
440
+ } catch(err) {
441
+ var errorMsg = "VideoComponent.getImgData:: Failed to call getImgData. " + err;
442
+ error(errorMsg);
443
+ throw new Error(errorMsg);
444
+ }
445
+ } else {
446
+ errorMsg = "VideoComponent.getImgData:: Component " + this.id + " does not exist.";
447
+ error(errorMsg);
448
+ throw new Error(errorMsg);
449
+ }
450
+ };
451
+ }
452
+
453
+
454
+ function Publisher (id, replacedDivId, properties) {
455
+ this.superClass = VideoComponent;
456
+ this.superClass(id, replacedDivId);
457
+ this._style = {
458
+ showMicButton: true,
459
+ showSettingsButton: true,
460
+ showCameraToggleButton: true,
461
+ nameDisplayMode: "auto",
462
+ buttonDisplayMode: "auto",
463
+ backgroundImageURI: null
464
+ };
465
+
466
+ this.modified = false;
467
+
468
+ if (properties && properties.hasOwnProperty("style")) {
469
+ this.setStyle(properties['style']);
470
+ this.modified = true;
471
+ }
472
+
473
+ this.properties = properties;
474
+ this.loaded = false;
475
+ this.panelId = null;
476
+ this.gain = 50;
477
+
478
+ if(properties && properties.hasOwnProperty("microphoneGain")) {
479
+ this.gain = parseInt(properties["microphoneGain"], 10);
480
+ }
481
+
482
+ this.enableMicrophone = function() {
483
+ this.publishAudio(true);
484
+ };
485
+ this.disableMicrophone = function() {
486
+ this.publishAudio(false);
487
+ };
488
+ this.setMicrophoneGain = function(value) {
489
+ var component = document.getElementById(this.id);
490
+ if (!this.loaded) {
491
+ this.gain = value;
492
+ this.modified = true;
493
+ } else if (component) {
494
+ try {
495
+ component.setMicGain(value);
496
+ } catch (err) {
497
+ var errorMsg = "Microphone gain adjustment on publisher "+this.id+" failed";
498
+ error(errorMsg);
499
+ throw new Error(errorMsg);
500
+ }
501
+ } else {
502
+ errorMsg = "Publisher "+ this.id + " does not exist.";
503
+ error(errorMsg);
504
+ throw new Error(errorMsg);
505
+ }
506
+ return this;
507
+ };
508
+ this.getMicrophoneGain = function() {
509
+ var component = document.getElementById(this.id);
510
+ if (!this.loaded) {
511
+ return this.gain;
512
+ } else if (component) {
513
+ try {
514
+ return component.getMicGain();
515
+ } catch (err) {
516
+ var errorMsg = "Microphone gain adjustment on publisher "+this.id+" failed";
517
+ error(errorMsg);
518
+ throw new Error(errorMsg);
519
+ }
520
+ } else {
521
+ errorMsg = "Publisher "+ this.id + " does not exist.";
522
+ error(errorMsg);
523
+ throw new Error(errorMsg);
524
+ }
525
+ };
526
+ this.getEchoCancellationMode = function() {
527
+ debug("Publisher.getEchoCancellationMode()");
528
+
529
+ var mode = "";
530
+ var component = document.getElementById(this.id);
531
+ if (!this.loaded) {
532
+ return "unknown";
533
+ } else if (component) {
534
+ try {
535
+ mode = component.getEchoCancellationMode();
536
+ } catch (err) {
537
+ var errorMsg = "Getting echo cancellation mode for publisher " + this.id + " failed. " + err;
538
+ error(errorMsg);
539
+ throw new Error(errorMsg);
540
+ }
541
+ } else {
542
+ errorMsg = "Publisher "+ this.id + " does not exist.";
543
+ error(errorMsg);
544
+ throw new Error(errorMsg);
545
+ }
546
+ return mode;
547
+ };
548
+ this.publishAudio = function(publishAudioBool) {
549
+ debug("Publisher.publishAudio()");
550
+ if (!this.loaded) {
551
+ this.audioPublished = publishAudioBool;
552
+ this.modified = true;
553
+ } else {
554
+ setStreamProperty(this.id, "publishAudio", publishAudioBool);
555
+ }
556
+ };
557
+ this.publishVideo = function(publishVideoBool) {
558
+ debug("Publisher.publishVideo()");
559
+ if (!this.loaded) {
560
+ this.videoPublished = publishVideoBool;
561
+ this.modified = true;
562
+ } else {
563
+ setStreamProperty(this.id, "publishVideo", publishVideoBool);
564
+ }
565
+ };
566
+ this.setCamera = function(camera) {
567
+ // Private function
568
+ debug("Publisher.setCamera(" + camera + ")");
569
+ setDevice(this.id, camera, true);
570
+ };
571
+ this.setMicrophone = function(microphone) {
572
+ // Private function
573
+ debug("Publisher.setMicrophone(" + microphone + ")");
574
+ setDevice(this.id, microphone, false);
575
+ };
576
+
577
+ }
578
+
579
+
580
+ function Subscriber (stream, id, replacedDivId, properties) {
581
+ this.superClass = VideoComponent;
582
+ this.superClass(id, replacedDivId);
583
+ this._style = {
584
+ nameDisplayMode: "auto",
585
+ buttonDisplayMode: "auto",
586
+ backgroundImageURI: null
587
+ };
588
+
589
+ this.modified = false;
590
+
591
+ if (properties && properties.hasOwnProperty("style")) {
592
+ this.setStyle(properties['style']);
593
+ this.modified = true;
594
+ }
595
+
596
+ this.stream = stream;
597
+ this.properties = properties;
598
+ this.loaded = false;
599
+ this.audioVolume = 50;
600
+
601
+ var _isAudioSubscribed = true;
602
+ var _isVideoSubscribed = true;
603
+
604
+ if(properties) {
605
+ if(properties.hasOwnProperty("subscribeToAudio") && (properties["subscribeToAudio"] == "false" || properties["subscribeToAudio"] == false)) {
606
+ _isAudioSubscribed = false;
607
+ }
608
+
609
+ if(properties.hasOwnProperty("subscribeToVideo") && (properties["subscribeToVideo"] == "false" || properties["subscribeToVideo"] == false)) {
610
+ _isVideoSubscribed = false;
611
+ }
612
+
613
+ if(properties.hasOwnProperty("audioVolume")) {
614
+ this.audioVolume = parseInt(properties["audioVolume"], 10);
615
+ }
616
+ }
617
+
618
+ this.enableAudio = function() {
619
+ this.subscribeToAudio(true);
620
+ };
621
+ this.disableAudio = function() {
622
+ this.subscribeToAudio(false);
623
+ };
624
+ this.setAudioVolume = function(value) {
625
+ var component = document.getElementById(this.id);
626
+ if (!this.loaded) {
627
+ this.audioVolume = value;
628
+ } else if (component) {
629
+ try {
630
+ component.setAudioVolume(value);
631
+ } catch (err) {
632
+ var errorMsg = "Volume adjustment on subscriber "+this.id+" failed";
633
+ error(errorMsg);
634
+ throw new Error(errorMsg);
635
+ }
636
+ } else {
637
+ errorMsg = "Subscriber "+ this.id + " does not exist.";
638
+ error(errorMsg);
639
+ throw new Error(errorMsg);
640
+ }
641
+ return this;
642
+ };
643
+ this.getAudioVolume = function() {
644
+ var component = document.getElementById(this.id);
645
+ if (!this.loaded) {
646
+ return this.audioVolume;
647
+ }
648
+ if (component) {
649
+ try {
650
+ return component.getAudioVolume();
651
+ } catch (err) {
652
+ var errorMsg = "Volume adjustment on subscriber "+this.id+" failed";
653
+ error(errorMsg);
654
+ throw new Error(errorMsg);
655
+ }
656
+ } else {
657
+ errorMsg = "Subscriber "+ this.id + " does not exist.";
658
+ error(errorMsg);
659
+ throw new Error(errorMsg);
660
+ }
661
+ return this;
662
+ };
663
+
664
+ /**
665
+ * Internal function to toggle the subscribeToAudio that respects
666
+ * the developer's state of subscribing
667
+ */
668
+ this._subscribeToAudio = function(subscribeAudioBool, isTokBox) {
669
+ debug("Subscriber.subscribeToAudio()");
670
+ if(!isTokBox || _isAudioSubscribed) {
671
+ if(!this.loaded) {
672
+ this.audioSubscribed = subscribeAudioBool;
673
+ this.modified = true;
674
+ } else {
675
+ setStreamProperty(this.id, "subscribeToAudio", subscribeAudioBool);
676
+ }
677
+ }
678
+ };
679
+ this.subscribeToAudio = function(subscribeAudioBool) {
680
+ _isAudioSubscribed = subscribeAudioBool;
681
+ this._subscribeToAudio(_isAudioSubscribed, false);
682
+ };
683
+
684
+ /**
685
+ * Internal function to toggle the subscribeToVideo that respects
686
+ * the developer's state of subscribing
687
+ */
688
+ this._subscribeToVideo = function(subscribeVideoBool, isTokBox) {
689
+ debug("Subscriber.subscribeToVideo()");
690
+ if(!isTokBox || _isVideoSubscribed) {
691
+ if(!this.loaded) {
692
+ this.videoSubscribed = subscribeVideoBool;
693
+ this.modified = true;
694
+ } else {
695
+ setStreamProperty(this.id, "subscribeToVideo", subscribeVideoBool);
696
+ }
697
+ }
698
+ };
699
+ this.subscribeToVideo = function(subscribeVideoBool) {
700
+ _isVideoSubscribed = subscribeVideoBool;
701
+ this._subscribeToVideo(_isVideoSubscribed, false);
702
+ };
703
+
704
+ this.changeOrientation = function(orientation) {
705
+ // private function
706
+ debug("Subscriber.changeOrientation()");
707
+ setStreamProperty(this.id, "changeOrientation", orientation);
708
+ };
709
+ }
710
+
711
+ function DevicePanel (id, replacedDivId, component, properties) {
712
+ this.superClass = UIComponent;
713
+ this.superClass(id, replacedDivId);
714
+
715
+ if (component) {
716
+ this.publisher = component; //publisher is deprecated
717
+ this.component = component;
718
+ } else {
719
+ this.publisher = null;
720
+ this.component = component;
721
+ }
722
+ this.parentCreated = false;
723
+ this.properties = properties;
724
+ }
725
+
726
+ function Camera (name, status) {
727
+ this.name = name;
728
+ this.status = status;
729
+ }
730
+
731
+ function Microphone (name, status) {
732
+ this.name = name;
733
+ this.status = status;
734
+ }
735
+
736
+ function Group (sessionId,groupId) {
737
+ this.superClass = EventDispatcher;
738
+ this.superClass();
739
+
740
+ this.groupId = groupId;
741
+ this.sessionId = sessionId;
742
+ this.enableEchoSuppression = function() {
743
+ debug("Group.enableEchoSuppresion()");
744
+ setEchoSuppressionEnabled(this.sessionId, this.groupId, true);
745
+ };
746
+ this.disableEchoSuppression = function() {
747
+ debug("Group.disableEchoSuppression()");
748
+ setEchoSuppressionEnabled(this.sessionId, this.groupId, false);
749
+ };
750
+
751
+ this.getGroupProperties = function() {
752
+ debug("Group.getGroupProperties()");
753
+ return getGroupProperties(this.sessionId, this.groupId);
754
+ };
755
+
756
+ }
757
+
758
+ function EchoSuppression(isEnabled){
759
+ this.isEnabled = isEnabled;
760
+ }
761
+
762
+ function Multiplexer(outputStreams,switchType,switchTimeout)
763
+ {
764
+ this.numOutputStreams = outputStreams;
765
+ this.switchType = switchType;
766
+ this.switchTimeout = switchTimeout;
767
+ }
768
+
769
+ function GroupProperties(group) {
770
+ this.echoSuppression = new EchoSuppression(group.echoSuppressionEnabled);
771
+ this.multiplexer = new Multiplexer(group.multiplexerNumOutputStreams,group.multiplexerSwitchType,group.multiplexerSwitchTimeout);
772
+ }
773
+
774
+ function Archive (archiveId, type, title, sessionId, status) {
775
+ this.archiveId = archiveId;
776
+ this.type = type;
777
+ this.title = title;
778
+ this.sessionId = sessionId;
779
+ var stateManager;
780
+ if (status == "sessionRecordingInProgress") {
781
+ this.recording = true;
782
+ this.status = "open";
783
+ }
784
+ else {
785
+ this.recording = false;
786
+ this.status = status;
787
+ }
788
+
789
+ this.startPlayback = function(loop) {
790
+ if (!loop) {
791
+ loop = false;
792
+ }
793
+ debug("Archive.startPlayback() : " + loop);
794
+ var controllerId = "controller_" + sessionId;
795
+ var connection = TB.sessions[sessionId].connection;
796
+ if (!loadedArchives[sessionId][this.archiveId]) {
797
+ var errorMsg = "Archive.startPlayback :: Archive not loaded.";
798
+ error(errorMsg);
799
+ throw new Error(errorMsg);
800
+ }
801
+ if (controllerId && connection && connection.connectionId) {
802
+ try {
803
+ var controller = document.getElementById(controllerId);
804
+ controller.startPlayback(this.archiveId, loop);
805
+ } catch(err) {
806
+ errorMsg = "Archive.startPlayback :: " + err;
807
+ error(errorMsg);
808
+ throw new Error(errorMsg);
809
+ }
810
+ } else {
811
+ errorMsg = "Archive.startPlayback :: Connection required to play back an archive.";
812
+ error(errorMsg);
813
+ throw new Error(errorMsg);
814
+ }
815
+ };
816
+
817
+ this.stopPlayback = function() {
818
+ debug("Archive.stopPlayback()");
819
+ var controllerId = "controller_" + sessionId;
820
+ var connection = TB.sessions[sessionId].connection;
821
+ if (controllerId && connection && connection.connectionId) {
822
+ try {
823
+ var controller = document.getElementById(controllerId);
824
+ controller.stopPlayback(this.archiveId);
825
+ } catch(err) {
826
+ var errorMsg = "Archive.stopPlayback :: " + err;
827
+ error(errorMsg);
828
+ throw new Error(errorMsg);
829
+ }
830
+ } else {
831
+ errorMsg = "Archive.stopPlayback :: Connection required to stop playing back an archive.";
832
+ error(errorMsg);
833
+ throw new Error(errorMsg);
834
+ }
835
+ };
836
+
837
+ this.getStateManager = function() {
838
+ debug("Archive.getStateManager() " + archiveId);
839
+
840
+ if (stateManager) return stateManager;
841
+
842
+ else {
843
+ var controllerId = "controller_" + sessionId;
844
+ var connection = TB.sessions[sessionId].connection;
845
+ if (controllerId && connection && connection.connectionId) {
846
+ stateManager = new StateManager(controllerId, archiveId);
847
+ return stateManager;
848
+ }
849
+ }
850
+
851
+ var errorMsg = "Archive.getStateManager :: Connection required to getStateManager. "
852
+ + "Make sure that this archive was loaded in a Session.";
853
+ error(errorMsg);
854
+ throw new Error(errorMsg);
855
+ };
856
+ }
857
+
858
+ function Recorder(id, replacedDivId, properties) {
859
+ this.superClass = VideoComponent;
860
+ this.superClass(id, replacedDivId);
861
+
862
+ this._style = {
863
+ buttonDisplayMode: "auto",
864
+ showCameraToggleButton: true,
865
+ showControlBar: true,
866
+ showMicButton: true,
867
+ showPlayCounter: true,
868
+ showRecordCounter: true,
869
+ showSaveButton: true,
870
+ showSettingsButton: true
871
+ };
872
+
873
+ this.id = id;
874
+ this.properties = properties;
875
+
876
+ this.saveArchive = function() {
877
+ var recorderElement = document.getElementById(this.id);
878
+ recorderElement.save();
879
+ };
880
+
881
+ this.setCamera = function(camera) {
882
+ debug("Recorder.setCamera(" + camera + ")");
883
+ setDevice(this.id, camera, true);
884
+ };
885
+ this.setMicrophone = function(microphone) {
886
+ debug("Recorder.setMicrophone(" + microphone + ")");
887
+ setDevice(this.id, microphone, false);
888
+ };
889
+
890
+ this.stopRecording = function() {
891
+ recorderElement = document.getElementById(this.id);
892
+ recorderElement.stopRecording();
893
+ };
894
+
895
+ this.startRecording = function(title) {
896
+ recorderElement = document.getElementById(this.id);
897
+ recorderElement.startRecording(title);
898
+ };
899
+
900
+ this.startPlaying = function() {
901
+ debug("Recorder.startPlaying()");
902
+ try {
903
+ var recorderElement = document.getElementById(this.id);
904
+ recorderElement.startPlaying();
905
+ } catch(err) {
906
+ var errorMsg = "Recorder.startPlaying :: " + err;
907
+ error(errorMsg);
908
+ throw new Error(errorMsg);
909
+ }
910
+ };
911
+
912
+ this.stopPlaying = function() {
913
+ debug("Recorder.stopPlaying()");
914
+ try {
915
+ var recorderElement = document.getElementById(this.id);
916
+ recorderElement.stopPlaying();
917
+ } catch(err) {
918
+ var errorMsg = "Recorder.stopPlaying :: " + err;
919
+ error(errorMsg);
920
+ throw new Error(errorMsg);
921
+ }
922
+ };
923
+
924
+ this.setTitle = function (title) {
925
+ var component = document.getElementById(this.id);
926
+ if (!this.loaded) {
927
+ this._title = title;
928
+ this.modified = true;
929
+ } else if (component) {
930
+ try {
931
+ component.setTitle(title);
932
+ } catch (err) {
933
+ var errorMsg = "Setting archive title on Recorder "+this.id+" failed.";
934
+ error(errorMsg);
935
+ throw new Error(errorMsg);
936
+ }
937
+ } else {
938
+ errorMsg = "Recorder "+ this.id + " does not exist.";
939
+ error(errorMsg);
940
+ throw new Error(errorMsg);
941
+ }
942
+ };
943
+
944
+ }
945
+
946
+
947
+ function Player(id, replacedDivId, properties) {
948
+ this.superClass = VideoComponent;
949
+ this.superClass(id, replacedDivId);
950
+
951
+ this._style = {
952
+ showPlayButton: true,
953
+ showStopButton: true,
954
+ showSpeakerButton: true
955
+ };
956
+
957
+ this.id = id;
958
+ this.properties = properties;
959
+ this.archiveId;
960
+
961
+ this.loadArchive = function(archiveId) {
962
+ if (archiveId) {
963
+ if (this.loaded) {
964
+ try {
965
+ var player = document.getElementById(this.id);
966
+ player.loadArchive(archiveId);
967
+ this.archiveId = archiveId;
968
+ } catch(err) {
969
+ var errorMsg = "Player.loadArchive :: " + err;
970
+ error(errorMsg);
971
+ throw new Error(errorMsg);
972
+ }
973
+ } else {
974
+ this._archiveId = archiveId;
975
+ }
976
+ } else {
977
+ errorMsg = "Player.loadArchive :: Archive id required to load an archive.";
978
+ error(errorMsg);
979
+ throw new Error(errorMsg);
980
+ }
981
+
982
+ };
983
+
984
+ this.play = function() {
985
+ if (this.loaded) {
986
+ try {
987
+ var player = document.getElementById(this.id);
988
+ player.startPlayback();
989
+ } catch(err) {
990
+ var errorMsg = "Player.play :: " + err;
991
+ error(errorMsg);
992
+ throw new Error(errorMsg);
993
+ }
994
+ } else {
995
+ this._play = true;
996
+ }
997
+ };
998
+
999
+ this.stop = function() {
1000
+ if (this.loaded) {
1001
+ try {
1002
+ var player = document.getElementById(this.id);
1003
+ player.stopPlayback();
1004
+ } catch(err) {
1005
+ var errorMsg = "Player.stop :: " + err;
1006
+ error(errorMsg);
1007
+ throw new Error(errorMsg);
1008
+ }
1009
+ } else {
1010
+ this._play = false;
1011
+ }
1012
+ };
1013
+
1014
+ this.pause = function() {
1015
+ if (this.loaded) {
1016
+ try {
1017
+ var player = document.getElementById(this.id);
1018
+ player.pausePlayback();
1019
+ } catch(err) {
1020
+ var errorMsg = "Player.pause :: " + err;
1021
+ error(errorMsg);
1022
+ throw new Error(errorMsg);
1023
+ }
1024
+ } else {
1025
+ this._play = false;
1026
+ }
1027
+ };
1028
+
1029
+ }
1030
+
1031
+ function DeviceManager (apiKey) {
1032
+ this.superClass = EventDispatcher;
1033
+ this.superClass();
1034
+
1035
+ this.apiKey = apiKey;
1036
+
1037
+ this.panels = {};
1038
+
1039
+ this.showMicSettings = true;
1040
+ this.showCamSettings = true;
1041
+
1042
+ var DEVICE_PANEL_WIDTH = 360;
1043
+ var DEVICE_PANEL_HEIGHT = 270;
1044
+ var DEVICE_PANEL_WIDTH_NO_CHROME = 340;
1045
+ var DEVICE_PANEL_HEIGHT_NO_CHROME = 230;
1046
+
1047
+ this.detectDevices = function() {
1048
+ debug("DeviceManager.detectDevices()");
1049
+ if (!deviceDetectorId) {
1050
+ var params = {};
1051
+ params.allowscriptaccess = "always";
1052
+
1053
+ deviceDetectorId = "opentok_deviceDetector";
1054
+ var attributes = {};
1055
+ attributes.id = deviceDetectorId;
1056
+
1057
+ var properties = {};
1058
+
1059
+ swfobject.addDomLoadEvent(function() {
1060
+ var div = document.createElement('div');
1061
+ div.setAttribute('id', deviceDetectorId);
1062
+ div.style.display = "none";
1063
+ document.body.appendChild(div);
1064
+
1065
+ swfobject.embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_devicedetectorwidget.swf?partnerId="+apiKey, deviceDetectorId, 1, 1, "10.0.0", false, properties, params, attributes);
1066
+ });
1067
+ } else {
1068
+ try {
1069
+ var deviceDetector = document.getElementById(deviceDetectorId);
1070
+ deviceDetector.detectDevices();
1071
+ } catch(err) {
1072
+ error(err);
1073
+ throw new Error("DeviceManager.detectDevices() :: Failed to locate existing device detector " + err);
1074
+ }
1075
+ }
1076
+ };
1077
+
1078
+ this.displayPanel = function(replaceElementId, component, properties) {
1079
+ debug("DeviceManager.displayPanel(" + replaceElementId + ")");
1080
+
1081
+ var panelId;
1082
+ if (component) panelId = "displayPanel_" + component.id;
1083
+ else panelId = "displayPanel_global";
1084
+
1085
+ // If this is a publisher update the panelId in the publisher object
1086
+ if (component && TB.sessions) {
1087
+ for (var i in TB.sessions) {
1088
+ if (TB.sessions[i].hasOwnProperty("publishers") && TB.sessions[i].publishers[component.id]) {
1089
+ TB.sessions[i].publishers[component.id].panelId = panelId;
1090
+ }
1091
+ }
1092
+ }
1093
+
1094
+ var existingElement = document.getElementById(panelId);
1095
+
1096
+ if (existingElement) {
1097
+ warn("DeviceManager.displayPanel :: there is already a device panel" + (component ? " for this component" : ""));
1098
+ return this.panels[panelId];
1099
+ }
1100
+
1101
+ var parentCreated = false;
1102
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1103
+ var params = {};
1104
+ params.allowscriptaccess = "always";
1105
+
1106
+ var width = DEVICE_PANEL_WIDTH;
1107
+ var height = DEVICE_PANEL_HEIGHT;
1108
+ if ("showCloseButton" in propertiesCopy) {
1109
+ if (propertiesCopy["showCloseButton"] == false) {
1110
+ width = DEVICE_PANEL_WIDTH_NO_CHROME;
1111
+ height = DEVICE_PANEL_HEIGHT_NO_CHROME;
1112
+ }
1113
+ } else {
1114
+ propertiesCopy["showCloseButton"] = true;
1115
+ }
1116
+
1117
+ if(!("showMicSettings" in propertiesCopy)) {
1118
+ propertiesCopy["showMicSettings"] = this.showMicSettings;
1119
+ }
1120
+
1121
+ if(!("showCamSettings" in propertiesCopy)) {
1122
+ propertiesCopy["showCamSettings"] = this.showCamSettings;
1123
+ }
1124
+
1125
+ if(!replaceElementId) {
1126
+ // If they didn't specify a replaceElementId then we will create a new element
1127
+ replaceElementId = 'devicePanel_replace_div';
1128
+ var replaceDiv = document.createElement('div');
1129
+ replaceDiv.setAttribute('id', replaceElementId);
1130
+
1131
+ var parentDiv = document.createElement('div');
1132
+ parentDiv.setAttribute('id', 'devicePanel_parent_' + (component ? component.id : 'global'));
1133
+ parentDiv.style.position = "absolute";
1134
+
1135
+ var yOffset = ("pageYOffset" in window && typeof( window.pageYOffset ) == 'number') ? window["pageYOffset"] :
1136
+ (document.body && document.body.scrollTop) ? document.body.scrollTop :
1137
+ (document.documentElement && document.documentElement.scrollTop) ? document.documentElement.scrollTop :
1138
+ 0;
1139
+ var winHeight = ("innerHeight" in window) ? window.innerHeight :
1140
+ (document.documentElement && document.documentElement.offsetHeight) ? document.documentElement.offsetHeight :
1141
+ DEVICE_PANEL_HEIGHT;
1142
+ yOffset += (winHeight * 0.20); // 20% down the current screen
1143
+
1144
+ parentDiv.style.top = yOffset + "px";
1145
+ parentDiv.style.left = "50%";
1146
+ parentDiv.style.width = width + "px";
1147
+ parentDiv.style.height = height + "px";
1148
+ parentDiv.style.marginLeft = (0 - width/2) + "px";
1149
+ parentDiv.style.marginTop = (0 - height/4) + "px";
1150
+ if ("zIndex" in propertiesCopy) {
1151
+ parentDiv.style.zIndex = propertiesCopy["zIndex"];
1152
+ delete propertiesCopy["zIndex"];
1153
+ } else {
1154
+ parentDiv.style.zIndex = highZ()+1;
1155
+ }
1156
+ document.body.appendChild(parentDiv);
1157
+ parentCreated = true;
1158
+ parentDiv.appendChild(replaceDiv);
1159
+ }
1160
+
1161
+ var replaceElement = document.getElementById(replaceElementId);
1162
+ if(!replaceElement) {
1163
+ var errorMsg = "DeviceManager.displayPanel :: replaceElementId does not exist in DOM.";
1164
+ error(errorMsg);
1165
+ throw new Error(errorMsg);
1166
+ }
1167
+
1168
+ var devicePanel;
1169
+ if (this.panels[panelId]) this.removePanel(this.panels[panelId]);
1170
+ if (component) devicePanel = new DevicePanel(panelId, replaceElementId, component, propertiesCopy);
1171
+ else devicePanel = new DevicePanel(panelId, replaceElementId, null, propertiesCopy);
1172
+
1173
+ devicePanel.parentCreated = parentCreated;
1174
+ this.panels[panelId] = devicePanel;
1175
+
1176
+ var attributes = {};
1177
+ attributes.id = devicePanel.id;
1178
+ attributes.style = "outline:none;";
1179
+
1180
+ propertiesCopy["devicePanelId"] = panelId;
1181
+
1182
+ if (propertiesCopy.wmode) {
1183
+ params.wmode = propertiesCopy.wmode;
1184
+ delete propertiesCopy["wmode"];
1185
+ } else {
1186
+ params.wmode = "transparent";
1187
+ }
1188
+
1189
+ embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_devicewidget.swf?partnerId="+this.apiKey, replaceElementId, width, height, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes);
1190
+
1191
+ return devicePanel;
1192
+ };
1193
+
1194
+ this.removePanel = function(devicePanel) {
1195
+ if (!devicePanel.hasOwnProperty("id")) {
1196
+ var errorMsg = "DeviceManager.removePanel :: invalid DevicePanel object";
1197
+ error(errorMsg);
1198
+ throw new Error(errorMsg);
1199
+ }
1200
+
1201
+ debug("DeviceManager.removePanel(" + devicePanel.id + ")");
1202
+
1203
+ var devicePanelElement = document.getElementById(devicePanel.id);
1204
+ if (!devicePanelElement) {
1205
+ errorMsg = "DeviceManager.removePanel :: DevicePanel does not exist in DOM";
1206
+ error(errorMsg);
1207
+ throw new Error(errorMsg);
1208
+ }
1209
+ var parentElement = devicePanelElement.parentNode;
1210
+ var parentCreated = devicePanel.parentCreated;
1211
+
1212
+ for (var dp in this.panels) {
1213
+ if (this.panels[dp].hasOwnProperty("id") && dp == devicePanel.id) {
1214
+ var panel = this.panels[dp];
1215
+ unloadComponent(this.panels[dp]);
1216
+ delete this.panels[dp];
1217
+
1218
+ var action = function() {
1219
+ if (panel.publisher && TB.sessions) {
1220
+ for (var i in TB.sessions) {
1221
+ if (TB.sessions[i].hasOwnProperty("disconnect") && TB.sessions[i].publishers[panel.publisher.id]) {
1222
+ TB.sessions[i].publishers[panel.publisher.id].panelId = null;
1223
+ }
1224
+ }
1225
+ }
1226
+ };
1227
+
1228
+ // The event handler is called asynchronously after 2 milliseconds.
1229
+ setTimeout(action, 2);
1230
+ }
1231
+ }
1232
+
1233
+ if (parentCreated) {
1234
+ // Remove the parent because we created it
1235
+ try {
1236
+ var parentNode = parentElement.parentNode;
1237
+ parentNode.removeChild(parentElement);
1238
+ } catch (err) {
1239
+ errorMsg = "Failed to clean up the parent of the device panel " + err;
1240
+ error(errorMsg);
1241
+ throw new Error(errorMsg);
1242
+ }
1243
+ }
1244
+ };
1245
+
1246
+ }
1247
+
1248
+ function RecorderManager (apiKey) {
1249
+
1250
+ var recorderCount = 1;
1251
+ var playerCount = 1;
1252
+
1253
+ this.recorders = {};
1254
+ this.players = {};
1255
+ this.apiKey = apiKey;
1256
+
1257
+ var DEFAULT_WIDTH = 320;
1258
+ var DEFAULT_HEIGHT = 271;
1259
+ var CONTROL_BAR_HEIGHT = 31;
1260
+
1261
+ this.displayRecorder = function(token, replaceElementId, properties) {
1262
+
1263
+ if (!token) {
1264
+ errorMsg = "RecorderManager.displayRecorder :: Token required to displayRecorder";
1265
+ error(errorMsg);
1266
+ throw new Error(errorMsg);
1267
+ }
1268
+
1269
+ var recorderId = "recorder_" + apiKey + "_" + recorderCount++;
1270
+
1271
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1272
+ propertiesCopy["token"] = token;
1273
+ propertiesCopy["partnerId"] = apiKey;
1274
+ propertiesCopy["recorderId"] = recorderId;
1275
+
1276
+ if (propertiesCopy.hasOwnProperty("style")) {
1277
+ var showControlBar = propertiesCopy.style.showControlBar;
1278
+ propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style));
1279
+ }
1280
+
1281
+ var params = {};
1282
+ params.allowscriptaccess = "always";
1283
+ if (propertiesCopy.wmode){
1284
+ params.wmode = propertiesCopy.wmode;
1285
+ delete propertiesCopy["wmode"];
1286
+ } else {
1287
+ params.wmode = "transparent";
1288
+ }
1289
+
1290
+ var attributes = {};
1291
+ attributes.id = recorderId;
1292
+ attributes.style = "outline:none;";
1293
+
1294
+ if (!propertiesCopy.width || isNaN(propertiesCopy.width)) {
1295
+ propertiesCopy.width = DEFAULT_WIDTH;
1296
+ }
1297
+ if (!propertiesCopy.height || isNaN(propertiesCopy.height)) {
1298
+ propertiesCopy.height = DEFAULT_HEIGHT;
1299
+ if (showControlBar == false) {
1300
+ propertiesCopy.height -= CONTROL_BAR_HEIGHT;
1301
+ }
1302
+ }
1303
+
1304
+ var createReplaceElement = false;
1305
+ if (!replaceElementId) {
1306
+ // Create a new element for the publisher and append it to the body
1307
+ replaceElementId = "recorder_replace_" + recorderCount;
1308
+ createReplaceElement = true;
1309
+ }
1310
+
1311
+ swfobject.addDomLoadEvent(function() {
1312
+ if (createReplaceElement) {
1313
+ var div = document.createElement('div');
1314
+ div.setAttribute('id', replaceElementId);
1315
+ document.body.appendChild(div);
1316
+ }
1317
+
1318
+ 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);
1319
+ });
1320
+
1321
+ this.recorders[recorderId] = new Recorder(recorderId, replaceElementId, propertiesCopy);
1322
+
1323
+ return this.recorders[recorderId];
1324
+ };
1325
+
1326
+ this.removeRecorder = function(recorder) {
1327
+ if (!recorder) {
1328
+ var errorMsg = "Session.removeRecorder :: recorder cannot be null";
1329
+ error(errorMsg);
1330
+ throw new Error(errorMsg);
1331
+ }
1332
+ debug("Session.removeRecorder(" + recorder.id + ")");
1333
+
1334
+ unloadComponent(recorder);
1335
+ delete this.recorders[recorder.id];
1336
+ };
1337
+
1338
+ this.displayPlayer = function(archiveId, token, replaceElementId, properties) {
1339
+
1340
+ if (!archiveId) {
1341
+ errorMsg = "RecorderManager.displayPlayer :: Valid ArchiveId required";
1342
+ error(errorMsg);
1343
+ throw new Error(errorMsg);
1344
+ }
1345
+
1346
+ var playerId = "player_" + apiKey + "_" + playerCount++;
1347
+
1348
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1349
+ propertiesCopy["token"] = token;
1350
+ propertiesCopy["archiveId"] = archiveId;
1351
+ propertiesCopy["partnerId"] = apiKey;
1352
+ propertiesCopy["playerId"] = playerId;
1353
+
1354
+ if (propertiesCopy.hasOwnProperty("style")) {
1355
+ var showControlBar = propertiesCopy.style.showControlBar;
1356
+ propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style));
1357
+ }
1358
+
1359
+ var params = {};
1360
+ params.allowscriptaccess = "always";
1361
+ if (propertiesCopy.wmode){
1362
+ params.wmode = propertiesCopy.wmode;
1363
+ delete propertiesCopy["wmode"];
1364
+ } else {
1365
+ params.wmode = "transparent";
1366
+ }
1367
+
1368
+ var attributes = {};
1369
+ attributes.id = playerId;
1370
+ attributes.style = "outline:none;";
1371
+
1372
+ if (!propertiesCopy.width || isNaN(propertiesCopy.width)) {
1373
+ propertiesCopy.width = DEFAULT_WIDTH;
1374
+ }
1375
+ if (!propertiesCopy.height || isNaN(propertiesCopy.height)) {
1376
+ propertiesCopy.height = DEFAULT_HEIGHT;
1377
+ if (showControlBar == false) {
1378
+ propertiesCopy.height -= CONTROL_BAR_HEIGHT;
1379
+ }
1380
+ }
1381
+ if (!propertiesCopy.autoPlay) {
1382
+ propertiesCopy.autoPlay = false;
1383
+ }
1384
+ var createReplaceElement = false;
1385
+ if (!replaceElementId) {
1386
+ // Create a new element for the player and append it to the body
1387
+ replaceElementId = "player_replace_" + playerCount;
1388
+ createReplaceElement = true;
1389
+ }
1390
+
1391
+ swfobject.addDomLoadEvent(function() {
1392
+ if (createReplaceElement) {
1393
+ var div = document.createElement('div');
1394
+ div.setAttribute('id', replaceElementId);
1395
+ document.body.appendChild(div);
1396
+ }
1397
+
1398
+ 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);
1399
+ });
1400
+
1401
+ this.players[playerId] = new Player(playerId, replaceElementId, propertiesCopy);
1402
+
1403
+ return this.players[playerId];
1404
+ };
1405
+
1406
+ this.removePlayer = function(player) {
1407
+ if (!player) {
1408
+ var errorMsg = "Session.removePlayer :: player cannot be null";
1409
+ error(errorMsg);
1410
+ throw new Error(errorMsg);
1411
+ }
1412
+ debug("Session.removePlayer(" + player.id + ")");
1413
+
1414
+ unloadComponent(player);
1415
+ delete this.players[player.id];
1416
+ };
1417
+
1418
+ }
1419
+
1420
+ function Session (sessionId) {
1421
+ this.superClass = EventDispatcher;
1422
+ this.superClass();
1423
+
1424
+ this.sessionId = sessionId;
1425
+ this.connection;
1426
+ this.subscribers = {};
1427
+ this.publishers = {};
1428
+ this.apiKey;
1429
+ this.capabilities;
1430
+ this.connected = false;
1431
+ this.connecting = false;
1432
+
1433
+ var publisherCount = 1;
1434
+ var subscriberCount = 1;
1435
+ var DEFAULT_WIDTH = 264;
1436
+ var DEFAULT_HEIGHT = 198;
1437
+ var controllerId;
1438
+ var stateManager;
1439
+
1440
+ this.connect = function(apiKey, token, properties) {
1441
+ if (this.connecting) {
1442
+ warn("Session.connect :: Patience, please.");
1443
+ return;
1444
+ }
1445
+
1446
+ debug("Session.connect(" + apiKey + ")");
1447
+
1448
+ if (!TB.checkSystemRequirements()) {
1449
+ var errorMsg = "Session.connect :: Flash Player Version 10+ required";
1450
+ error(errorMsg);
1451
+ throw new Error(errorMsg);
1452
+ }
1453
+ if (!apiKey) {
1454
+ errorMsg = "Session.connect :: API key required to connect";
1455
+ error(errorMsg);
1456
+ throw new Error(errorMsg);
1457
+ }
1458
+ if (!token) {
1459
+ errorMsg = "Session.connect :: Token required to connect";
1460
+ error(errorMsg);
1461
+ throw new Error(errorMsg);
1462
+ }
1463
+ if (this.connected) {
1464
+ warn("Session.connect :: Session already connected");
1465
+ return;
1466
+ }
1467
+
1468
+ this.connecting = true;
1469
+
1470
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1471
+
1472
+ this.apiKey = apiKey;
1473
+ this.token = token;
1474
+ this.properties = properties;
1475
+ var params = {};
1476
+ params.allowscriptaccess = "always";
1477
+ if (propertiesCopy.wmode) {
1478
+ params.wmode = propertiesCopy.wmode;
1479
+ delete propertiesCopy["wmode"];
1480
+ }
1481
+
1482
+ if (propertiesCopy.connectionData) {
1483
+ propertiesCopy.connectionData = encodeURIComponent(propertiesCopy.connectionData);
1484
+ }
1485
+
1486
+ controllerId = "controller_" + this.sessionId;
1487
+ var attributes = {};
1488
+ attributes.id = controllerId;
1489
+
1490
+ propertiesCopy["sessionId"] = this.sessionId;
1491
+ propertiesCopy["token"] = this.token;
1492
+
1493
+ var replaceId = "replace_" + this.sessionId;
1494
+ swfobject.addDomLoadEvent(function() {
1495
+ var div = document.createElement('div');
1496
+ div.setAttribute('id', replaceId);
1497
+ div.style.display = "none";
1498
+ document.body.appendChild(div);
1499
+ var nowDate = new Date();
1500
+ propertiesCopy["startTime"] = nowDate.getTime();
1501
+ swfobject.embedSWF(WIDGET_URL + "/v0.91.46.b5f48f1/flash/f_controllerwidget.swf?partnerId="+apiKey, replaceId, 1, 1, MIN_FLASH_VERSION, false, propertiesCopy, params, attributes);
1502
+ });
1503
+ if (window.location.protocol == "file:") {
1504
+ setTimeout("TB.controllerLoadCheck()", 8000);
1505
+ }
1506
+ };
1507
+
1508
+ this.disconnect = function() {
1509
+ debug("Session.disconnect()");
1510
+
1511
+ if (!controllerId || this.connecting) {
1512
+ warn("Session.disconnect :: No connection to disconnect");
1513
+ return;
1514
+ }
1515
+
1516
+ // Disconnect controller
1517
+ var controller = document.getElementById(controllerId);
1518
+ if (controller) {
1519
+ if (!isUnloading) {
1520
+ try {
1521
+ controller.cleanupView();
1522
+
1523
+ } catch(e) {
1524
+ var errorMsg = "Session.disconnect :: Failed to disconnect - " + e;
1525
+ error(errorMsg);
1526
+ throw new Error(errorMsg);
1527
+ }
1528
+ }
1529
+ } else {
1530
+ warn("Session.disconnect :: No connection to disconnect");
1531
+ }
1532
+ };
1533
+
1534
+ this.disconnectComponents = function() {
1535
+ debug("Session.disconnectComponents() - disconnecting publishers and subscribers");
1536
+ // As part of cleaning up connections, disconnect any publishers and subscribers
1537
+
1538
+ for (var publisher in this.publishers) {
1539
+ if (this.publishers[publisher].hasOwnProperty("id"))
1540
+ disconnectComponent(this.publishers[publisher]);
1541
+ }
1542
+
1543
+ for (var subscriber in this.subscribers) {
1544
+ if (this.subscribers[subscriber].hasOwnProperty("id"))
1545
+ disconnectComponent(this.subscribers[subscriber]);
1546
+ }
1547
+ };
1548
+
1549
+ this.cleanup = function() {
1550
+ debug("Session.cleanup()");
1551
+ for (var publisher in this.publishers) {
1552
+ if (this.publishers[publisher].hasOwnProperty("id"))
1553
+ this.unpublish(this.publishers[publisher]);
1554
+ }
1555
+ for (var subscriber in this.subscribers) {
1556
+ if (this.subscribers[subscriber].hasOwnProperty("id"))
1557
+ this.unsubscribe(this.subscribers[subscriber]);
1558
+ }
1559
+ };
1560
+
1561
+ this.cleanupConnection = function() {
1562
+ // private function
1563
+ debug("Session.cleanupConnection() - removing controller");
1564
+ this.connection = null;
1565
+
1566
+ if (!controllerId) {
1567
+ warn("Session.cleanup :: No connection to clean up");
1568
+ return;
1569
+ }
1570
+
1571
+ if (document.getElementById(controllerId)) {
1572
+ setTimeout(function() { removeSWF(controllerId, "TB.sessionDisconnected :: "); controllerId = null; }, 0); // must be asynchronous
1573
+ } else {
1574
+ warn("Session.cleanup :: No connection to clean up");
1575
+ }
1576
+ };
1577
+
1578
+
1579
+ this.publish = function(replaceElementId, properties) {
1580
+ debug("Session.publish(" + replaceElementId + "):" + properties);
1581
+
1582
+
1583
+ if (!this.connection || !this.connection.connectionId) {
1584
+ var errorMsg = "Session.publish :: Connection required to publish";
1585
+ error(errorMsg);
1586
+ throw new Error(errorMsg);
1587
+ }
1588
+ if (!replaceElementId) {
1589
+ // Create a new element for the publisher and append it to the body
1590
+ var div = document.createElement('div');
1591
+ replaceElementId = "publisher_replace_" + this.sessionId + "_" + publisherCount;
1592
+ div.setAttribute('id', replaceElementId);
1593
+ document.body.appendChild(div);
1594
+ }
1595
+
1596
+ // Check the name & data properties for length
1597
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1598
+
1599
+ if (propertiesCopy["name"] != undefined && propertiesCopy["name"].length > 1000) {
1600
+ errorMsg = "Session.publish :: name property longer than 1000 chars.";
1601
+ error(errorMsg);
1602
+ throw new Error(errorMsg);
1603
+ }
1604
+
1605
+ if (propertiesCopy["data"] != undefined && propertiesCopy["data"].length > 1000) {
1606
+ errorMsg = "Session.publish :: data property longer than 1000 chars.";
1607
+ error(errorMsg);
1608
+ throw new Error(errorMsg);
1609
+ }
1610
+
1611
+ var publisherId = "publisher_" + this.sessionId + "_" + publisherCount++;
1612
+ var publisher = new Publisher(publisherId, replaceElementId, propertiesCopy);
1613
+ return this._embedPublisher(publisher);
1614
+ };
1615
+
1616
+ // This function is not intended for publish use, it is so that we can republish when someone clicks
1617
+ // the deny button
1618
+ this._embedPublisher = function (publisher) {
1619
+ var replaceElement = document.getElementById(publisher.replacedDivId);
1620
+ if(!replaceElement) {
1621
+ errorMsg = "Session.publish :: replaceElementId does not exist in DOM.";
1622
+ error(errorMsg);
1623
+ throw new Error(errorMsg);
1624
+ }
1625
+
1626
+ var params = {};
1627
+ params.allowscriptaccess = "always";
1628
+ params.cameraSelected = cameraSelected;
1629
+
1630
+ if (publisher.properties.wmode){
1631
+ params.wmode = publisher.properties.wmode;
1632
+ delete publisher.properties["wmode"];
1633
+ } else {
1634
+ params.wmode = "transparent";
1635
+ }
1636
+
1637
+ if (publisher.properties.hasOwnProperty("style")) {
1638
+ publisher.properties.style = encodeURIComponent(JSONify(publisher.properties.style));
1639
+ }
1640
+
1641
+ var attributes = {};
1642
+ attributes.id = publisher.id;
1643
+ attributes.style = "outline:none;";
1644
+
1645
+ publisher.properties["publisherId"] = publisher.id;
1646
+ publisher.properties["connectionId"] = this.connection.connectionId;
1647
+ publisher.properties["sessionId"] = this.sessionId;
1648
+ publisher.properties["token"] = this.token;
1649
+ publisher.properties["cameraSelected"] = cameraSelected;
1650
+ publisher.properties["simulateMobile"] = TB.simulateMobile;
1651
+ publisher.properties["publishCapability"] = this.capabilities.publish;
1652
+
1653
+ if (!publisher.properties.width || isNaN(publisher.properties.width))
1654
+ publisher.properties.width = DEFAULT_WIDTH;
1655
+ if (!publisher.properties.height || isNaN(publisher.properties.height))
1656
+ publisher.properties.height = DEFAULT_HEIGHT;
1657
+ /** if (!publisher.properties.encodedWidth || isNaN(publisher.properties.encodedWidth))
1658
+ publisher.properties.encodedWidth = DEFAULT_WIDTH;
1659
+ if (!publisher.properties.encodedHeight || isNaN(publisher.properties.encodedHeight))
1660
+ publisher.properties.encodedHeight = DEFAULT_HEIGHT;
1661
+ */
1662
+ this.publishers[publisher.id] = publisher;
1663
+ var nowDate = new Date();
1664
+ publisher.properties["startTime"] = nowDate.getTime();
1665
+ 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);
1666
+
1667
+ return publisher;
1668
+ };
1669
+
1670
+
1671
+ this.unpublish = function(publisher) {
1672
+ if (!publisher) {
1673
+ var errorMsg = "Session.unpublish :: publisher cannot be null";
1674
+ error(errorMsg);
1675
+ throw new Error(errorMsg);
1676
+ }
1677
+ debug("Session.unpublish(" + publisher.id + ")");
1678
+
1679
+ if (publisher.panelId && deviceManager && deviceManager.panels[publisher.panelId]) {
1680
+ deviceManager.removePanel(deviceManager.panels[publisher.panelId]);
1681
+ }
1682
+
1683
+ unloadComponent(publisher);
1684
+ delete this.publishers[publisher.id];
1685
+ };
1686
+
1687
+ this.forceUnpublish = function(stream) {
1688
+ var streamId;
1689
+ if (stream && typeof(stream) == "string") {
1690
+ streamId = stream;
1691
+ } else if (stream && typeof(stream) == "object" && stream.hasOwnProperty("streamId")) {
1692
+ streamId = stream.streamId;
1693
+ } else {
1694
+ var errorMsg = "Session.forceUnpublish :: Invalid stream type";
1695
+ error(errorMsg);
1696
+ throw new Error(errorMsg);
1697
+ }
1698
+ debug("Session.forceUnpublish(" + streamId + ")");
1699
+
1700
+ if (streamId) {
1701
+ try {
1702
+ var controller = document.getElementById(controllerId);
1703
+ controller.forceUnpublish(streamId);
1704
+ } catch(err) {
1705
+ errorMsg = "Session.forceUnpublish :: "+ err;
1706
+ error(errorMsg);
1707
+ throw new Error(errorMsg);
1708
+ }
1709
+ } else {
1710
+ errorMsg = "Session.forceUnpublish :: Stream does not exist.";
1711
+ error(errorMsg);
1712
+ throw new Error(errorMsg);
1713
+ }
1714
+ };
1715
+
1716
+ this.subscribe = function(stream, replaceElementId, properties) {
1717
+ if (!this.connection || !this.connection.connectionId) {
1718
+ var errorMsg = "Session.subscribe :: Connection required to subscribe";
1719
+ error(errorMsg);
1720
+ throw new Error(errorMsg);
1721
+ }
1722
+
1723
+ if (!stream) {
1724
+ errorMsg = "Session.subscribe :: stream cannot be null";
1725
+ error(errorMsg);
1726
+ throw new Error(errorMsg);
1727
+ }
1728
+ if (!stream.hasOwnProperty("streamId")) {
1729
+ errorMsg = "Session.subscribe :: invalid stream object";
1730
+ error(errorMsg);
1731
+ throw new Error(errorMsg);
1732
+ }
1733
+ debug("Session.subscribe(" + stream.streamId + ")");
1734
+
1735
+ if (!replaceElementId) {
1736
+ // Create a new element for the subscriber and append it to the body
1737
+ var div = document.createElement('div');
1738
+ replaceElementId = "subscriber_replace_" + this.sessionId + "_" + subscriberCount;
1739
+ div.setAttribute('id', replaceElementId);
1740
+ document.body.appendChild(div);
1741
+ }
1742
+
1743
+ var replaceElement = document.getElementById(replaceElementId);
1744
+ if(!replaceElement) {
1745
+ errorMsg = "Session.subscribe :: replaceElementId does not exist in DOM.";
1746
+ error(errorMsg);
1747
+ throw new Error(errorMsg);
1748
+ }
1749
+
1750
+ var propertiesCopy = (properties) ? copyObject(properties) : {};
1751
+
1752
+ if( stream && stream.hasOwnProperty("type") && stream.type == "multiplexed") {
1753
+ propertiesCopy.mixedStreamURI = "live-lowlatency";
1754
+ }
1755
+
1756
+ var subscriberId = "subscriber_" + stream.streamId + "_" + subscriberCount++;
1757
+ var subscriber = new Subscriber(stream, subscriberId, replaceElementId, propertiesCopy);
1758
+
1759
+ var params = {};
1760
+ params.allowscriptaccess = "always";
1761
+ if (propertiesCopy.wmode){
1762
+ params.wmode = propertiesCopy.wmode;
1763
+ delete propertiesCopy["wmode"];
1764
+ } else {
1765
+ params.wmode = "transparent";
1766
+ }
1767
+
1768
+ if (propertiesCopy.hasOwnProperty("style")) {
1769
+ propertiesCopy.style = encodeURIComponent(JSONify(propertiesCopy.style));
1770
+ }
1771
+
1772
+ var attributes = {};
1773
+ attributes.id = subscriber.id;
1774
+ attributes.style = "outline:none;";
1775
+
1776
+ propertiesCopy["subscriberId"] = subscriberId;
1777
+ propertiesCopy["connectionId"] = this.connection.connectionId;
1778
+ propertiesCopy["sessionId"] = this.sessionId;
1779
+ propertiesCopy["streamId"] = stream.streamId;
1780
+ propertiesCopy["streamType"] = stream.type;
1781
+ propertiesCopy["name"] = stream.name;
1782
+ propertiesCopy["token"] = this.token;
1783
+ propertiesCopy["simulateMobile"] = TB.simulateMobile;
1784
+ propertiesCopy["isPublishing"] = (Object.keys(this.publishers).length > 0);
1785
+
1786
+ if(!stream.hasAudio) {
1787
+ propertiesCopy["subscribeToAudio"] = "false";
1788
+ }
1789
+ if(!stream.hasVideo) {
1790
+ propertiesCopy["subscribeToVideo"] = "false";
1791
+ }
1792
+ propertiesCopy["orientation"] = stream.orientation;
1793
+ propertiesCopy["peerId"] = stream.peerId;
1794
+
1795
+ if (!propertiesCopy.width || isNaN(propertiesCopy.width))
1796
+ propertiesCopy.width = DEFAULT_WIDTH;
1797
+ if (!propertiesCopy.height || isNaN(propertiesCopy.height))
1798
+ propertiesCopy.height = DEFAULT_HEIGHT;
1799
+
1800
+ this.subscribers[subscriber.id] = subscriber;
1801
+
1802
+ var nowDate = new Date();
1803
+ propertiesCopy["startTime"] = nowDate.getTime();
1804
+ 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);
1805
+
1806
+ return subscriber;
1807
+ };
1808
+
1809
+ this.unsubscribe = function(subscriber) {
1810
+ if (!subscriber) {
1811
+ var errorMsg = "Subscribe.unsubscribe :: subscriber cannot be null";
1812
+ error(errorMsg);
1813
+ throw new Error(errorMsg);
1814
+ }
1815
+ debug("Session.unsubscribe(" + subscriber.id + ")");
1816
+
1817
+ unloadComponent(subscriber);
1818
+ delete this.subscribers[subscriber.id];
1819
+ };
1820
+
1821
+ this.signal = function() {
1822
+ debug("Session.signal()");
1823
+ if (controllerId && this.connection && this.connection.connectionId) {
1824
+ try {
1825
+ var controller = document.getElementById(controllerId);
1826
+ controller.sendSignal();
1827
+ } catch(err) {
1828
+ var errorMsg = "Session.signal :: " + err;
1829
+ error(errorMsg);
1830
+ throw new Error(errorMsg);
1831
+ }
1832
+ } else {
1833
+ errorMsg = "Session.signal :: Connection required to signal.";
1834
+ error(errorMsg);
1835
+ throw new Error(errorMsg);
1836
+ }
1837
+ };
1838
+
1839
+
1840
+ this.forceDisconnect = function(connection) {
1841
+ if (connection) debug("Session.forceDisconnect(" + connection.connectionId + ")");
1842
+ var connectionId;
1843
+ if (connection && typeof(connection) == "string")
1844
+ connectionId = connection;
1845
+ else if (connection && typeof(connection) == "object" && connection.hasOwnProperty("connectionId"))
1846
+ connectionId = connection.connectionId;
1847
+ else {
1848
+ var errorMsg = "Session.forceDisconnect :: Invalid connection type";
1849
+ error(errorMsg);
1850
+ throw new Error(errorMsg);
1851
+ }
1852
+
1853
+ if (controllerId && this.connection && this.connection.connectionId) {
1854
+ try {
1855
+ var controller = document.getElementById(controllerId);
1856
+ controller.forceDisconnect(connectionId);
1857
+ } catch(err) {
1858
+ errorMsg = "Session.forceDisconnect :: "+ err;
1859
+ error(errorMsg);
1860
+ throw new Error(errorMsg);
1861
+ }
1862
+ } else {
1863
+ errorMsg = "Session.forceDisconnect :: Connection required to forceDisconnect.";
1864
+ error(errorMsg);
1865
+ throw new Error(errorMsg);
1866
+ }
1867
+ };
1868
+
1869
+ this.getSubscribersForStream = function(stream) {
1870
+ var res = null;
1871
+ if (!stream) {
1872
+ var errorMsg = "Session.getSubscribersForStream :: stream cannot be null";
1873
+ error(errorMsg);
1874
+ throw new Error(errorMsg);
1875
+ } else {
1876
+ var streamId;
1877
+ if (typeof(stream) == "string") {
1878
+ streamId = stream;
1879
+ } else if (typeof(stream) == "object" && stream.hasOwnProperty("streamId")) {
1880
+ streamId = stream.streamId;
1881
+ } else {
1882
+ errorMsg = "Session.getSubscribersForStream :: Invalid stream type";
1883
+ error(errorMsg);
1884
+ throw new Error(errorMsg);
1885
+ }
1886
+
1887
+ res = [];
1888
+ for (var sr in this.subscribers) {
1889
+ if (this.subscribers[sr].hasOwnProperty("stream") && this.subscribers[sr].stream.streamId == streamId)
1890
+ res.push(this.subscribers[sr]);
1891
+ }
1892
+ }
1893
+
1894
+ return res;
1895
+ };
1896
+
1897
+ this.getPublisherForStream = function(stream) {
1898
+ if (!stream) {
1899
+ var errorMsg = "Session.getPublisherForStream :: stream cannot be null";
1900
+ error(errorMsg);
1901
+ throw new Error(errorMsg);
1902
+ } else {
1903
+ var streamId;
1904
+ if (typeof(stream) == "string") {
1905
+ streamId = stream;
1906
+ } else if (typeof(stream) == "object" && stream.hasOwnProperty("streamId")) {
1907
+ streamId = stream.streamId;
1908
+ } else {
1909
+ errorMsg = "Session.getPublisherForStream :: Invalid stream type";
1910
+ error(errorMsg);
1911
+ throw new Error(errorMsg);
1912
+ }
1913
+
1914
+ for (var pub in this.publishers) {
1915
+ var publisher = document.getElementById(this.publishers[pub].id);
1916
+ if (publisher) {
1917
+ try {
1918
+ if (publisher.getStreamId() == streamId) return this.publishers[pub];
1919
+ } catch (err) {
1920
+ warn("Failed to get streamId for publisher: " + this.publishers[pub].id);
1921
+ }
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ return null;
1927
+ };
1928
+
1929
+ this.createArchive = function(apiKey, type, title) {
1930
+ debug("Session.createArchive()");
1931
+ if (controllerId && this.connection && this.connection.connectionId) {
1932
+ if (type == TB.PER_SESSION || type == TB.PER_STREAM) {
1933
+ try {
1934
+ var controller = document.getElementById(controllerId);
1935
+ controller.createArchive(apiKey, type, title);
1936
+ } catch(err) {
1937
+ errorMsg = "Session.createArchive :: " + err;
1938
+ error(errorMsg);
1939
+ throw new Error(errorMsg);
1940
+ }
1941
+ } else {
1942
+ errorMsg = "Session.createArchive :: Invalid type specfied.";
1943
+ error(errorMsg);
1944
+ throw new Error(errorMsg);
1945
+ }
1946
+ } else {
1947
+ errorMsg = "Session.createArchive :: Connection required to create an archive.";
1948
+ error(errorMsg);
1949
+ throw new Error(errorMsg);
1950
+ }
1951
+ };
1952
+
1953
+ this.loadArchive = function(archiveId) {
1954
+ debug("Session.loadArchive()");
1955
+ if (controllerId && this.connection && this.connection.connectionId) {
1956
+ try {
1957
+ var controller = document.getElementById(controllerId);
1958
+ controller.loadArchive(archiveId);
1959
+ } catch(err) {
1960
+ var errorMsg = "Session.loadArchive :: " + err;
1961
+ error(errorMsg);
1962
+ throw new Error(errorMsg);
1963
+ }
1964
+ } else {
1965
+ errorMsg = "Session.loadArchive :: Connection required to load an archive.";
1966
+ error(errorMsg);
1967
+ throw new Error(errorMsg);
1968
+ }
1969
+ };
1970
+
1971
+ this.startRecording = function(archive) {
1972
+ debug("Session.startRecording()");
1973
+ archive = createdArchives[this.sessionId][archive.archiveId];
1974
+ if (!archive) {
1975
+ var errorMsg = "Session.startRecording :: Archive not created.";
1976
+ error(errorMsg);
1977
+ throw new Error(errorMsg);
1978
+ }
1979
+ if (archive.type != TB.PER_SESSION) {
1980
+ errorMsg = "Session.startRecording :: Trying to record per session on a " + archive.type + " archive";
1981
+ error(errorMsg);
1982
+ throw new Error(errorMsg);
1983
+ }
1984
+ if (archive.recording) {
1985
+ warn("Session.startRecording :: Trying to start recording when the archive is already recording");
1986
+ return;
1987
+ }
1988
+ if (controllerId && this.connection && this.connection.connectionId) {
1989
+ try {
1990
+ var controller = document.getElementById(controllerId);
1991
+ controller.startRecordingSession(archive.archiveId);
1992
+ archive.recording = true;
1993
+ } catch(err) {
1994
+ errorMsg = "Session.startRecording :: " + err;
1995
+ error(errorMsg);
1996
+ throw new Error(errorMsg);
1997
+ }
1998
+ } else {
1999
+ errorMsg = "Session.startRecording :: Connection required to record an archive.";
2000
+ error(errorMsg);
2001
+ throw new Error(errorMsg);
2002
+ }
2003
+ };
2004
+
2005
+ this.stopRecording = function(archive) {
2006
+ debug("Session.stopRecording()");
2007
+ archive = createdArchives[this.sessionId][archive.archiveId];
2008
+ if (!archive) {
2009
+ var errorMsg = "Session.stopRecording :: Archive not created.";
2010
+ error(errorMsg);
2011
+ throw new Error(errorMsg);
2012
+ }
2013
+ if (archive.type != TB.PER_SESSION) {
2014
+ errorMsg = "Session.stopRecording :: Trying to stop recording per session on a " + archive.type + " archive";
2015
+ error(errorMsg);
2016
+ throw new Error(errorMsg);
2017
+ }
2018
+ if (controllerId && this.connection && this.connection.connectionId) {
2019
+ try {
2020
+ var controller = document.getElementById(controllerId);
2021
+ controller.stopRecordingSession(archive.archiveId);
2022
+ archive.recording = false;
2023
+ } catch(err) {
2024
+ errorMsg = "Session.stopRecording :: " + err;
2025
+ error(errorMsg);
2026
+ throw new Error(errorMsg);
2027
+ }
2028
+ } else {
2029
+ errorMsg = "Session.stopRecording :: Connection required to record an archive.";
2030
+ error(errorMsg);
2031
+ throw new Error(errorMsg);
2032
+ }
2033
+ };
2034
+
2035
+ this.closeArchive = function(archive) {
2036
+ debug("Session.closeArchive()");
2037
+ if (controllerId && this.connection && this.connection.connectionId) {
2038
+ try {
2039
+ var controller = document.getElementById(controllerId);
2040
+ controller.closeArchive(archive.archiveId);
2041
+ } catch(err) {
2042
+ var errorMsg = "Session.closeArchive :: " + err;
2043
+ error(errorMsg);
2044
+ throw new Error(errorMsg);
2045
+ }
2046
+ } else {
2047
+ errorMsg = "Session.closeArchive :: Connection required to close an archive.";
2048
+ error(errorMsg);
2049
+ throw new Error(errorMsg);
2050
+ }
2051
+ };
2052
+
2053
+ this.getStateManager = function() {
2054
+ debug("Session.getStateManager()");
2055
+
2056
+ if (stateManager) return stateManager;
2057
+ else if (controllerId && this.connection && this.connection.connectionId) {
2058
+ stateManager = new StateManager(controllerId);
2059
+ return stateManager;
2060
+ }
2061
+
2062
+ var errorMsg = "Session.getStateManager :: Connection required to getState. Wait for sessionConnected before you getStateManager.";
2063
+ error(errorMsg);
2064
+ throw new Error(errorMsg);
2065
+ };
2066
+ }
2067
+
2068
+ function StateManager(controllerId, archiveId) {
2069
+ this.superClass = EventDispatcher;
2070
+ this.superClass();
2071
+
2072
+ var MAX_KEYS = 20;
2073
+
2074
+ this.archiveId = archiveId;
2075
+
2076
+ this.set = function(key, value) {
2077
+ var values = key;
2078
+ if (archiveId) {
2079
+ var errorMsg = "StateManager.set :: not allowed on StateManager objects for archives.";
2080
+ error(errorMsg);
2081
+ throw new Error(errorMsg);
2082
+ }
2083
+ if (typeof(key) == "string" && (typeof(value) == "string" || value == null)) {
2084
+ values = {};
2085
+ values[key] = value;
2086
+ } else if (typeof(key) == "object" && value == null) {
2087
+ if (Object.keys(values).length > MAX_KEYS) {
2088
+ error("StateManager.set :: Maximum number of keys exceeded");
2089
+ this.dispatchEvent(new ChangeFailedEvent("changeFailed", 405, "Maximum number of keys exceeded", values));
2090
+ return;
2091
+ }
2092
+ } else {
2093
+ errorMsg = "StateManager.set :: Invalid parameters passed. set() takes either two string parameters or one object of key value pairs.";
2094
+ error(errorMsg);
2095
+ throw new Error(errorMsg);
2096
+ }
2097
+
2098
+ for (var k in values) {
2099
+ if (typeof(values[k]) != "string" && values[k] != null) {
2100
+ error("StateManager.set :: Invalid value " + values[k].toString() + " is not a string");
2101
+ this.dispatchEvent(new ChangeFailedEvent("changeFailed", 403, " Invalid value, value must be a string", values));
2102
+ return;
2103
+ }
2104
+ };
2105
+
2106
+ if (controllerId) {
2107
+ try {
2108
+ var controller = document.getElementById(controllerId);
2109
+ controller.setState(values);
2110
+ } catch (err) {
2111
+ errorMsg = "StateManager.set :: " + err;
2112
+ error(errorMsg);
2113
+ throw new Error(errorMsg);
2114
+ }
2115
+ }
2116
+ };
2117
+
2118
+ this.superAddEventListener = this.addEventListener;
2119
+ this.addEventListener = function(type, listener) {
2120
+ var key = false;
2121
+ if (type == "changed") {
2122
+ key = null;
2123
+ } else if (type.indexOf("changed:") == 0) {
2124
+ // Tell the controller which keys we want to subscribe to
2125
+ key = type.split(":")[1];
2126
+ }
2127
+
2128
+ if (key !== false) {
2129
+ if (archiveId) {
2130
+ key = "TB_archive_" + archiveId + "_";
2131
+ }
2132
+ // Tell the controller that we want to subscribe to all keys
2133
+ if (controllerId) {
2134
+ try {
2135
+ var controller = document.getElementById(controllerId);
2136
+ controller.subscribeToKeyChange(key);
2137
+ } catch(err) {
2138
+ var errorMsg = "StateManager.addEventListener :: " + err;
2139
+ error(errorMsg);
2140
+ throw new Error(errorMsg);
2141
+ }
2142
+ }
2143
+ }
2144
+
2145
+ this.superAddEventListener(type, listener);
2146
+ };
2147
+
2148
+ // Need to figure out how to know whether there are any event listeners for a key
2149
+ // if there are none then we can stop listening on the shared object, otherwise we should
2150
+ // keep listening.
2151
+ // this.superRemoveEventListener = this.removeEventListener;
2152
+ // this.removeEventListener = function(type, listener) {
2153
+ // var key = false;
2154
+ // if (type == "changed") {
2155
+ // key = null;
2156
+ // } else if (type.indexOf("changed:") == 0) {
2157
+ // // Tell the controller which keys we want to subscribe to
2158
+ // key = type.split(":")[1];
2159
+ // }
2160
+ //
2161
+ // if (key !== false) {
2162
+ // // Tell the controller that we want to subscribe to all keys
2163
+ // if (controllerId) {
2164
+ // try {
2165
+ // var controller = document.getElementById(controllerId);
2166
+ // controller.unsubscribeFromKeyChange(key);
2167
+ // } catch(err) {
2168
+ // var errorMsg = "StateManager.removeEventListener :: " + err;
2169
+ // error(errorMsg);
2170
+ // throw new Error(errorMsg);
2171
+ // }
2172
+ // }
2173
+ // }
2174
+ //
2175
+ // this.superRemoveEventListener(type, listener);
2176
+ // };
2177
+ }
2178
+
2179
+
2180
+ //--------------------------------------
2181
+ // PRIVATE HELPER FUNCTIONS
2182
+ //--------------------------------------
2183
+
2184
+ function setEchoSuppressionEnabled(sessionId, groupId, isEnabled) {
2185
+ try {
2186
+ var controller = document.getElementById("controller_" + sessionId);
2187
+ controller.setEchoSuppressionEnabled(groupId, isEnabled);
2188
+ } catch(err) {
2189
+ var errorMsg = "Group :: " + err;
2190
+ error(errorMsg);
2191
+ throw new Error(errorMsg);
2192
+ }
2193
+ }
2194
+
2195
+ function getGroupProperties(sessionId, groupId) {
2196
+ var groupProperties = null;
2197
+ try {
2198
+ var controller = document.getElementById("controller_" + sessionId);
2199
+ var groupObject = controller.getGroupProperties(groupId);
2200
+
2201
+ groupProperties = new GroupProperties(groupObject);
2202
+ } catch(err) {
2203
+ var errorMsg = "Group :: " + err;
2204
+ error(errorMsg);
2205
+ throw new Error(errorMsg);
2206
+ }
2207
+
2208
+ return groupProperties;
2209
+ }
2210
+
2211
+ function embedCallback (event) {
2212
+ if (!event.success) {
2213
+ error("Failed to embed SWF " + event.id);
2214
+ TB.exceptionHandler("Failed to embed SWF " + event.id, "Embed Failed", 2001);
2215
+ }
2216
+ }
2217
+
2218
+ function embedSWF(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) {
2219
+ if (!swfobject.hasFlashPlayerVersion(swfVersionStr)) {
2220
+ error("Flash Player " + swfVersionStr + " or higher required");
2221
+ TB.exceptionHandler("Flash Player " + swfVersionStr + " or higher required", "Embed Failed", 2001);
2222
+ return;
2223
+ }
2224
+
2225
+ swfobject.embedSWF(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, embedCallback);
2226
+ }
2227
+
2228
+ function createHandler(func, event) {
2229
+ return function() {
2230
+ if(func != null) {
2231
+ func(event);
2232
+ } else {
2233
+ error('Event handler is null');
2234
+ }
2235
+ };
2236
+ }
2237
+
2238
+ function flashdebug (str) {
2239
+ window.opentokdebug.debug("[FLASHDEBUG] opentok: " + str);
2240
+ }
2241
+
2242
+ function debug (str) {
2243
+ window.opentokdebug.debug("[DEBUG] opentok: " + str);
2244
+ }
2245
+
2246
+ function info (str) {
2247
+ window.opentokdebug.info("[INFO] opentok: " + str);
2248
+ }
2249
+
2250
+ function warn (str) {
2251
+ window.opentokdebug.warn("[WARN] opentok: " + str);
2252
+ }
2253
+
2254
+ function error (str) {
2255
+ window.opentokdebug.error("[ERROR] opentok: " + str);
2256
+ }
2257
+
2258
+ function traceOut (level, str) {
2259
+ var element = document.getElementById('opentok_console');
2260
+ if (element) element.innerHTML += (str + '<br>');
2261
+ }
2262
+
2263
+ function getConnectionFromConnectionId (connectionId) {
2264
+ if (connectionMap.hasOwnProperty(connectionId)) {
2265
+ var connection = connectionMap[connectionId];
2266
+ } else {
2267
+ connection = new Connection(connectionId, NaN, null);
2268
+ }
2269
+ return connection;
2270
+ }
2271
+
2272
+ function getStream(streamObject, sessionId) {
2273
+ 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);
2274
+ }
2275
+
2276
+ function getStreams (streamObjects, sessionId) {
2277
+ var streams = [];
2278
+ for (var i=0; i < streamObjects.length; i++) {
2279
+ streams.push(getStream(streamObjects[i], sessionId));
2280
+ }
2281
+
2282
+ return streams;
2283
+ }
2284
+
2285
+ function getArchive (archive, sessionId) {
2286
+ var newArchive = new Archive(archive.id, archive.type, archive.title, sessionId, archive.status);
2287
+ if (!createdArchives.hasOwnProperty(sessionId)) createdArchives[sessionId] = {};
2288
+ createdArchives[sessionId][archive.id] = newArchive;
2289
+
2290
+ return newArchive;
2291
+ }
2292
+
2293
+ function getConnections (connectionObjects) {
2294
+ var connections = [];
2295
+
2296
+ for (var i=0; i < connectionObjects.length; i++) {
2297
+ var connection = new Connection(connectionObjects[i].connectionId, connectionObjects[i].creationTime, connectionObjects[i].data);
2298
+ connections.push(connection);
2299
+
2300
+ connectionMap[connection.connectionId] = connection;
2301
+ };
2302
+
2303
+ return connections;
2304
+ }
2305
+
2306
+ function getGroups (sessionId,groupObjects) {
2307
+ var groups = [];
2308
+ for (var key in groupObjects) {
2309
+ if (groupObjects[key].hasOwnProperty("groupId"))
2310
+ groups.push(new Group(sessionId,groupObjects[key].groupId));
2311
+ }
2312
+
2313
+ return groups;
2314
+ }
2315
+
2316
+ function getCamera (cameraObj) {
2317
+ if (cameraObj.status == TB.ACTIVE) {
2318
+ return new Camera(cameraObj.name, TB.ACTIVE);
2319
+ } else if (cameraObj.status == TB.INACTIVE) {
2320
+ return new Camera(cameraObj.name, TB.INACTIVE);
2321
+ } else {
2322
+ return new Camera(cameraObj.name, TB.UNKNOWN);
2323
+ }
2324
+ }
2325
+
2326
+ function getMicrophone (microphoneObj) {
2327
+ return new Microphone(microphoneObj.name, microphoneObj.status);
2328
+ }
2329
+
2330
+ function getCameras (cameraObjects) {
2331
+ var cameras = new Array();
2332
+
2333
+ for (var i=0; i < cameraObjects.length; i++) {
2334
+ cameras.push(new Camera(cameraObjects[i].name, cameraObjects[i].status));
2335
+ };
2336
+
2337
+ return cameras;
2338
+ }
2339
+
2340
+ function getMicrophones (microphoneObjects) {
2341
+ var microphones = new Array();
2342
+
2343
+ for (var i=0; i < microphoneObjects.length; i++) {
2344
+ microphones.push(new Microphone(microphoneObjects[i].name, microphoneObjects[i].status));
2345
+ };
2346
+
2347
+ return microphones;
2348
+ }
2349
+
2350
+ function disconnectComponent(component) {
2351
+ if(!component.hasOwnProperty("id")){
2352
+ return;
2353
+ }
2354
+ var uicomponent = document.getElementById(component.id);
2355
+
2356
+ if (uicomponent) {
2357
+ try {
2358
+ uicomponent.cleanupView();
2359
+ } catch(e) {
2360
+ warn("Disconnecting " + component.id + " failed");
2361
+ }
2362
+ } else {
2363
+ warn("Disconnecting " + component.id + " failed");
2364
+ }
2365
+ }
2366
+
2367
+ function unloadComponent (component) {
2368
+ var uicomponent = document.getElementById(component.id);
2369
+ if (uicomponent) {
2370
+ try {
2371
+ uicomponent.cleanupView();
2372
+
2373
+ var parentNode = uicomponent.parentNode;
2374
+ parentNode.removeChild(uicomponent);
2375
+ } catch(e) {
2376
+ warn("Removing " + component.id + " failed " + e);
2377
+ }
2378
+ } else {
2379
+ warn("Element " + component.id + " does not exist");
2380
+ }
2381
+ }
2382
+
2383
+ function removeSWF (componentId, message) {
2384
+ try {
2385
+ if (componentId) {
2386
+ swfobject.removeSWF(componentId);
2387
+ componentId = null;
2388
+ }
2389
+ } catch(err) {
2390
+ var errorMsg = message + err;
2391
+ error(errorMsg);
2392
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2393
+ }
2394
+ }
2395
+
2396
+ function setStreamProperty (id, property, value) {
2397
+ var component = document.getElementById(id);
2398
+ if (component) {
2399
+ try {
2400
+ component.setStreamProperty(property, value);
2401
+ } catch (err) {
2402
+ var errorMsg = "Changing settings on component " + id + " failed.";
2403
+ error(errorMsg);
2404
+ throw new Error(errorMsg);
2405
+ }
2406
+ } else {
2407
+ errorMsg = "Component "+id + " does not exist.";
2408
+ error(errorMsg);
2409
+ throw new Error(errorMsg);
2410
+ }
2411
+ }
2412
+
2413
+ function setDevice (id, device, isCamera) {
2414
+ var component = document.getElementById(id);
2415
+ if (component) {
2416
+ try {
2417
+ if (isCamera) component.setCamera(device.name);
2418
+ else component.setMicrophone(device.name);
2419
+ } catch (err) {
2420
+ var errorMsg = "Changing hardware settings on publisher " + id + " failed.";
2421
+ error(errorMsg);
2422
+ throw new Error(errorMsg);
2423
+ }
2424
+ } else {
2425
+ errorMsg = "Publisher "+ id + " does not exist.";
2426
+ error(errorMsg);
2427
+ throw new Error(errorMsg);
2428
+ }
2429
+ }
2430
+
2431
+ // Find highest Z-index - via StackOverflow <http://bit.ly/dFaOw9>
2432
+ function highZ(parent, limit){
2433
+ limit = limit || Infinity;
2434
+ parent = parent || document.body;
2435
+ var who, temp, max= 1, A= [], i= 0;
2436
+ var children = parent.childNodes, length = children.length;
2437
+ while(i<length){
2438
+ who = children[i++];
2439
+ if (who.nodeType != 1) continue;
2440
+ if (deepCss(who,"position") !== "static") { // element nodes only
2441
+ temp = deepCss(who,"z-index");
2442
+ if (temp == "auto") { // z-index is auto, so not a new stacking context
2443
+ temp = highZ(who);
2444
+ } else {
2445
+ temp = parseInt(temp, 10) || 0;
2446
+ }
2447
+ } else { // non-positioned element, so not a new stacking context
2448
+ temp = highZ(who);
2449
+ }
2450
+ if (temp > max && temp <= limit) max = temp;
2451
+ }
2452
+ return max;
2453
+ }
2454
+
2455
+ // This function is only intended for highZ(). Other uses may be unpredictable.
2456
+ function deepCss(who, css) {
2457
+ var sty, val, dv= document.defaultView || window;
2458
+ if (who.nodeType == 1) {
2459
+ sty = css.replace(/\-([a-z])/g, function(a, b){
2460
+ return b.toUpperCase();
2461
+ });
2462
+ val = who.style[sty];
2463
+ if (!val) {
2464
+ if(who.currentStyle) val= who.currentStyle[sty];
2465
+ else if (dv.getComputedStyle) {
2466
+ val= dv.getComputedStyle(who,"").getPropertyValue(css);
2467
+ }
2468
+ }
2469
+ }
2470
+ return val || "";
2471
+ }
2472
+
2473
+ function createHiddenElement (form, key, value) {
2474
+ var hiddenField = document.createElement("input");
2475
+ hiddenField.setAttribute("name", key);
2476
+ hiddenField.setAttribute("value", value);
2477
+ hiddenField.setAttribute("type", "hidden");
2478
+ form.appendChild(hiddenField);
2479
+ }
2480
+
2481
+ function getDataForUIComponent (componentId) {
2482
+ try {
2483
+ var element = document.getElementById(componentId);
2484
+ if (element) {
2485
+ return element.fetchData();
2486
+ }
2487
+ } catch (err) {
2488
+ warn("Failed to get logs for " + componentId + " " + err);
2489
+ return "";
2490
+ }
2491
+ }
2492
+
2493
+ function JSONify(object) {
2494
+ // JSONify the style property
2495
+ var styleString = "{ ";
2496
+ for (var key in object) {
2497
+ if (typeof(object[key]) == "boolean")
2498
+ styleString += '"' + key + '":' + object[key] + ', ';
2499
+ else
2500
+ styleString += '"' + key + '":"' + object[key].toString() + '", ';
2501
+ };
2502
+ if (styleString.length > 1) {
2503
+ styleString = styleString.substring(0, styleString.length - 2) + " }";
2504
+ } else {
2505
+ styleString = "{}";
2506
+ }
2507
+
2508
+ return styleString;
2509
+ }
2510
+
2511
+ function copyObject(obj) {
2512
+ var newObj = (obj instanceof Array) ? [] : {};
2513
+ for (var i in obj) {
2514
+ if (i == 'clone') continue;
2515
+ if (obj[i] && typeof obj[i] == "object") {
2516
+ newObj[i] = copyObject(obj[i]);
2517
+ } else newObj[i] = obj[i];
2518
+ }
2519
+ return newObj;
2520
+ }
2521
+
2522
+
2523
+ //--------------------------------------
2524
+ // EVENT HANDLERS
2525
+ //--------------------------------------
2526
+
2527
+ this.isUnloading = false;
2528
+ window.onunload = function() {
2529
+ isUnloading = true;
2530
+ for (var i in TB.sessions) {
2531
+ if (TB.sessions[i].hasOwnProperty("disconnect")) {
2532
+ // Stop sessionDisconnectedHandler from happening. Was causing crashes on Safari.
2533
+ // We are just doing all the cleanup now.
2534
+ TB.sessionDisconnectedHandler = function() {};
2535
+
2536
+ TB.sessions[i].disconnect();
2537
+ TB.sessions[i].cleanupConnection();
2538
+ TB.sessions[i].cleanup();
2539
+ }
2540
+ }
2541
+ };
2542
+
2543
+
2544
+ //--------------------------------------
2545
+ // PRIVATE STATIC VARIABLES
2546
+ //--------------------------------------
2547
+
2548
+ var MIN_FLASH_VERSION = "10.0.0";
2549
+
2550
+ // Minimum width and height to fit the adobe settings UI
2551
+ var MIN_ADOBE_WIDTH = 215;
2552
+ var MIN_ADOBE_HEIGHT = 138;
2553
+
2554
+ var deviceManager;
2555
+ var recorderManager;
2556
+ var deviceDetectorId;
2557
+ var cameraSelected = false;
2558
+ var showingIssueForm = false;
2559
+
2560
+ var connectionMap = {};
2561
+
2562
+ var SUPPORT_SSL = "true";
2563
+
2564
+ var WIDGET_URL = "http://staging.tokbox.com";
2565
+
2566
+ if (SUPPORT_SSL == "true" && window.location.protocol == "https:") {
2567
+ WIDGET_URL = "https://staging.tokbox.com";
2568
+ }
2569
+
2570
+ var dispatcher = new EventDispatcher();
2571
+ var createdArchives = {};
2572
+ var loadedArchives = {};
2573
+
2574
+ var controllerLoaded = false;
2575
+
2576
+ return {
2577
+
2578
+ //--------------------------------------
2579
+ // TB PUBLIC STATIC VARIABLES
2580
+ //--------------------------------------
2581
+
2582
+ sessions: {},
2583
+ groups: {},
2584
+
2585
+ LOG: 5,
2586
+ DEBUG: 4,
2587
+ INFO: 3,
2588
+ WARN: 2,
2589
+ ERROR: 1,
2590
+ NONE: 0,
2591
+
2592
+ // Activity Status for cams/mics
2593
+ ACTIVE: "active",
2594
+ INACTIVE: "inactive",
2595
+ UNKNOWN: "unknown",
2596
+
2597
+ // Archive types
2598
+ PER_SESSION: "perSession",
2599
+ PER_STREAM: "perStream",
2600
+
2601
+ // TB Events
2602
+ EXCEPTION: "exception",
2603
+
2604
+ // Session Events
2605
+ SESSION_CONNECTED: "sessionConnected",
2606
+ SESSION_DISCONNECTED: "sessionDisconnected",
2607
+ STREAM_CREATED: "streamCreated",
2608
+ STREAM_DESTROYED: "streamDestroyed",
2609
+ CONNECTION_CREATED: "connectionCreated",
2610
+ CONNECTION_DESTROYED: "connectionDestroyed",
2611
+ SIGNAL_RECEIVED: "signalReceived",
2612
+ STREAM_PROPERTY_CHANGED: "streamPropertyChanged",
2613
+ MICROPHONE_LEVEL_CHANGED: "microphoneLevelChanged",
2614
+ ARCHIVE_CREATED: "archiveCreated",
2615
+ ARCHIVE_CLOSED: "archiveClosed",
2616
+ ARCHIVE_LOADED: "archiveLoaded",
2617
+ ARCHIVE_SAVED: "archiveSaved",
2618
+ SESSION_RECORDING_STARTED: "sessionRecordingStarted",
2619
+ SESSION_RECORDING_STOPPED: "sessionRecordingStopped",
2620
+ SESSION_RECORDING_IN_PROGRESS: "sessionRecordingInProgress",
2621
+ STREAM_RECORDING_IN_PROGRESS: "streamRecordingInProgress",
2622
+ SESSION_NOT_RECORDING: "sessionNotRecording",
2623
+ STREAM_RECORDING_STARTED: "streamRecordingStarted",
2624
+ STREAM_RECORDING_STOPPED: "streamRecordingStopped",
2625
+ PLAYBACK_STARTED: "playbackStarted",
2626
+ PLAYBACK_STOPPED: "playbackStopped",
2627
+ RECORDING_STARTED: "recordingStarted",
2628
+ RECORDING_STOPPED: "recordingStopped",
2629
+ // Group Events
2630
+ GROUP_PROPERTIES_UPDATED: "groupPropertiesUpdated",
2631
+
2632
+ // Publisher Events
2633
+ RESIZE: "resize",
2634
+ SETTINGS_BUTTON_CLICK: "settingsButtonClick",
2635
+ DEVICE_INACTIVE: "deviceInactive",
2636
+ ACCESS_ALLOWED: "accessAllowed",
2637
+ ACCESS_DENIED: "accessDenied",
2638
+ ECHO_CANCELLATION_MODE_CHANGED: "echoCancellationModeChanged",
2639
+
2640
+ // DeviceManager Events
2641
+ DEVICES_DETECTED: "devicesDetected",
2642
+
2643
+ // DevicePanel Events
2644
+ DEVICES_SELECTED: "devicesSelected",
2645
+ CLOSE_BUTTON_CLICK: "closeButtonClick",
2646
+
2647
+ HAS_REQUIREMENTS: 1,
2648
+ OLD_FLASH_VERSION: 0,
2649
+
2650
+ // Stream types
2651
+ BASIC_STREAM: "basic",
2652
+ MULTIPLEXED_STREAM: "multiplexed",
2653
+ ARCHIVED: "archive",
2654
+
2655
+ // Global group ID
2656
+ GLOBAL_GROUP: "global",
2657
+
2658
+ // Multiplexer Switch Type
2659
+ MULTIPLEXER_TIMEOUT_BASED_SWITCH: 0,
2660
+ MULTIPLEXER_ACTIVITY_BASED_SWITCH: 1,
2661
+
2662
+ simulateMobile: false,
2663
+
2664
+ //--------------------------------------
2665
+ // TB STATIC FUNCTIONS
2666
+ //--------------------------------------
2667
+
2668
+ setLogLevel: function(value) {
2669
+ window.opentokdebug.setLevel(value);
2670
+ if (value == this.NONE) window.opentokdebug.setCallback(null);
2671
+ else window.opentokdebug.setCallback(traceOut, true);
2672
+ debug("TB.setLogLevel(" + value + ")" );
2673
+ },
2674
+
2675
+
2676
+ log: function(str) {
2677
+ window.opentokdebug.log("[LOG] opentok: " + str);
2678
+ },
2679
+
2680
+ initSession: function(sessionId) {
2681
+ debug("TB.initSession(" + sessionId + ")");
2682
+ if (sessionId == null || sessionId == "") {
2683
+ var errorMsg = "TB.initSession :: sessionId cannot be null";
2684
+ error(errorMsg);
2685
+ throw new Error(errorMsg);
2686
+ }
2687
+
2688
+ if (!this.sessions.hasOwnProperty(sessionId)) {
2689
+ this.sessions[sessionId] = new Session(sessionId);
2690
+ }
2691
+
2692
+ return this.sessions[sessionId];
2693
+ },
2694
+
2695
+ initDeviceManager: function(apiKey) {
2696
+ debug("TB.initDeviceManager(" + apiKey + ")");
2697
+ if (!apiKey) {
2698
+ var errorMsg = "TB.initDeviceManager :: apiKey cannot be null";
2699
+ error(errorMsg);
2700
+ throw new Error(errorMsg);
2701
+ }
2702
+ if (!deviceManager) {
2703
+ deviceManager = new DeviceManager(apiKey);
2704
+ }
2705
+ return deviceManager;
2706
+ },
2707
+
2708
+ initRecorderManager: function(apiKey) {
2709
+ debug("TB.initRecorderManager(" + apiKey + ")");
2710
+ if (!apiKey) {
2711
+ var errorMsg = "TB.initRecorderManager :: apiKey cannot be null";
2712
+ error(errorMsg);
2713
+ throw new Error(errorMsg);
2714
+ }
2715
+ if (!recorderManager) {
2716
+ recorderManager = new RecorderManager(apiKey);
2717
+ }
2718
+ return recorderManager;
2719
+ },
2720
+
2721
+ addEventListener: function(type, callback) {
2722
+ debug("TB.addEventListener(" + type + ")");
2723
+ dispatcher.addEventListener(type, callback);
2724
+ },
2725
+
2726
+ removeEventListener: function(type, callback) {
2727
+ debug("TB.removeEventListener(" + type + ")");
2728
+ dispatcher.removeEventListener(type, callback);
2729
+ },
2730
+
2731
+ dispatchEvent: function(event) {
2732
+ debug("TB.dispatchEvent()");
2733
+ event.target = this;
2734
+ dispatcher.dispatchEvent(event);
2735
+ },
2736
+
2737
+ checkSystemRequirements: function() {
2738
+ debug("TB.checkSystemRequirements()");
2739
+ return swfobject.hasFlashPlayerVersion(MIN_FLASH_VERSION) ? this.HAS_REQUIREMENTS : this.OLD_FLASH_VERSION;
2740
+ },
2741
+
2742
+ //--------------------------------------
2743
+ // FLASH CALLBACK HANDLERS
2744
+ //--------------------------------------
2745
+
2746
+ // TB callbacks
2747
+ exceptionHandler: function(msg, title, errorCode) {
2748
+ error("TB.exception :: title: " + title + " msg: " + msg + " errorCode: " + errorCode);
2749
+ try {
2750
+ this.dispatchEvent(new ExceptionEvent(this.EXCEPTION, msg, title, errorCode));
2751
+ } catch(err) {
2752
+ var errorMsg = "TB.exception :: Failed to dispatch exception - " + err;
2753
+ error(errorMsg);
2754
+ // Don't throw an error because this is asynchronous
2755
+ // don't do an exceptionHandler because that would be recursive
2756
+ }
2757
+ },
2758
+
2759
+ // private callback
2760
+ controllerLoadedHandler: function() {
2761
+ controllerLoaded = true;
2762
+ },
2763
+
2764
+ controllerLoadCheck: function(event) {
2765
+ if (!controllerLoaded) {
2766
+ var confirmMsg = "The connection timed out. Make sure that you have allowed this page in the"
2767
+ + "Flash Player Global Settings Manager. Go to:";
2768
+ adobeURL = "http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html";
2769
+ prompt(confirmMsg, adobeURL);
2770
+ }
2771
+ },
2772
+
2773
+ // private callback
2774
+ flashLogger: function(msg) {
2775
+ flashdebug(msg);
2776
+ },
2777
+
2778
+ // private callback
2779
+ destroyStreamHandler: function(sessionId, streamObjects) {
2780
+ debug("TB.destroyStream");
2781
+ try {
2782
+ var session = this.sessions[sessionId];
2783
+ var streams = getStreams(streamObjects, sessionId);
2784
+
2785
+ var action = function() {
2786
+ for (var i = 0; i < streams.length; i++) {
2787
+ var publisher = session.getPublisherForStream(streams[i]);
2788
+ if (publisher) {
2789
+ session.unpublish(publisher);
2790
+ }
2791
+ }
2792
+ };
2793
+
2794
+ // The event handler is called asynchronously after 2 milliseconds.
2795
+ setTimeout(action, 2);
2796
+ } catch(err) {
2797
+ var errorMsg = "TB.destroyStream :: " + err;
2798
+ error(errorMsg);
2799
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2800
+ }
2801
+ },
2802
+
2803
+ // Session callbacks
2804
+ sessionConnectedHandler: function(sessionId, connectionId, connectionObjects, streamObjects, groupObjects, capabilities, connectionQuality, p_archives) {
2805
+ debug("TB.sessionConnected");
2806
+ try {
2807
+ var session = this.sessions[sessionId];
2808
+ for(var i=0, len = connectionObjects.length; i < len; i++) {
2809
+ connection = connectionObjects[i];
2810
+ if(connection.connectionId == connectionId) {
2811
+ session.connection = new Connection(connectionId, connection.creationTime, connection.data);
2812
+ break;
2813
+ }
2814
+ }
2815
+ session.connected = true;
2816
+ session.connecting = false;
2817
+ session.connection.quality = connectionQuality;
2818
+ session.capabilities = capabilities;
2819
+ var connections = getConnections(connectionObjects);
2820
+ var streams = getStreams(streamObjects, session.sessionId);
2821
+ var groups = getGroups(sessionId,groupObjects);
2822
+ for (var i=0; i < groups.length; i++) {
2823
+ this.groups[sessionId + "_" + groups[i].groupId] = groups[i];
2824
+ }
2825
+
2826
+ var archives = [];
2827
+ for (var i=0; i < p_archives.length; i++) {
2828
+ var newArchive = getArchive(p_archives[i], sessionId);
2829
+ archives.push(newArchive);
2830
+ };
2831
+
2832
+ session.dispatchEvent(new SessionConnectEvent(this.SESSION_CONNECTED, connections, streams, groups, archives));
2833
+ }
2834
+ catch(err) {
2835
+ var errorMsg = "TB.sessionConnected :: "+err;
2836
+ error(errorMsg);
2837
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2838
+ }
2839
+ },
2840
+
2841
+ sessionDisconnectedHandler: function(sessionId, reason) {
2842
+ debug("TB.sessionDisconnected(" + reason + ")");
2843
+ try {
2844
+ var session = this.sessions[sessionId];
2845
+ session.disconnectComponents();
2846
+ session.cleanupConnection();
2847
+ session.connected = false;
2848
+
2849
+ var event = new SessionDisconnectEvent(this.SESSION_DISCONNECTED, reason, true);
2850
+ session.dispatchEvent(event);
2851
+
2852
+ var defaultAction = function() {
2853
+ if (!event.isDefaultPrevented()) {
2854
+ session.cleanup();
2855
+ }
2856
+ };
2857
+
2858
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
2859
+ setTimeout(defaultAction, 2);
2860
+ } catch(err) {
2861
+ var errorMsg = "TB.sessionDisconnected :: " + err;
2862
+ error(errorMsg);
2863
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2864
+ }
2865
+ },
2866
+
2867
+ streamCreatedHandler: function(sessionId, streamObjects, reason) {
2868
+ debug("TB.streamCreated");
2869
+ try {
2870
+ var session = this.sessions[sessionId];
2871
+
2872
+ var streams = getStreams(streamObjects, sessionId);
2873
+ session.dispatchEvent(new StreamEvent(this.STREAM_CREATED, streams, reason));
2874
+
2875
+ //notify publisher if there's an archive in flight
2876
+ var myArchives = createdArchives[sessionId];
2877
+ for (var bob in myArchives) {
2878
+ for (var i=0; i<streams.length; i++) {
2879
+ if (streams[i].connection.connectionId == connection.connectionId) {
2880
+ for (var pub in session.publishers) {
2881
+ if (session.publishers[pub].hasOwnProperty("id")) {
2882
+ var publisher = document.getElementById(session.publishers[pub].id);
2883
+ if (bob && myArchives[bob] && myArchives[bob].type == TB.PER_SESSION && myArchives[bob].recording == true) {
2884
+ publisher.signalRecordingStarted();
2885
+ }
2886
+ }
2887
+ }
2888
+ }
2889
+ }
2890
+ }
2891
+
2892
+ } catch(err) {
2893
+ var errorMsg = "TB.streamCreated :: "+err;
2894
+ error(errorMsg);
2895
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2896
+ }
2897
+ },
2898
+
2899
+ streamDestroyedHandler: function(sessionId, streamObjects, reason) {
2900
+ debug("TB.streamDestroyed");
2901
+ try {
2902
+ var session = this.sessions[sessionId];
2903
+ var streams = getStreams(streamObjects, sessionId);
2904
+ var event = new StreamEvent(this.STREAM_DESTROYED, streams, reason, true);
2905
+ session.dispatchEvent(event);
2906
+
2907
+ var defaultAction = function() {
2908
+ if (!event.isDefaultPrevented()) {
2909
+ for (var i = 0; i < event.streams.length; i++) {
2910
+ var subscribers = session.getSubscribersForStream(event.streams[i]);
2911
+ for (var j = 0; j < subscribers.length; j++) {
2912
+ session.unsubscribe(subscribers[j]);
2913
+ }
2914
+ var publisher = session.getPublisherForStream(event.streams[i]);
2915
+ if (publisher) {
2916
+ session.unpublish(publisher);
2917
+ }
2918
+ }
2919
+ }
2920
+ };
2921
+
2922
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
2923
+ setTimeout(defaultAction, 2);
2924
+ } catch(err) {
2925
+ var errorMsg = "TB.streamDestroyed :: " + err;
2926
+ error(errorMsg);
2927
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2928
+ }
2929
+ },
2930
+
2931
+ streamPropertyChangedHandler: function(sessionId, streamObj, changedProperty, oldValue, newValue) {
2932
+ debug("TB.streamPropertyChangedHandler");
2933
+
2934
+ var session = this.sessions[sessionId];
2935
+ var stream = getStream(streamObj, sessionId);
2936
+
2937
+ var event = new StreamPropertyChangedEvent(this.STREAM_PROPERTY_CHANGED, stream, changedProperty, oldValue, newValue);
2938
+
2939
+ session.dispatchEvent(event);
2940
+
2941
+ try {
2942
+ var subscriber;
2943
+ if ("hasAudio" == changedProperty) {
2944
+ for (var componentId in session.subscribers) {
2945
+ subscriber = session.subscribers[componentId];
2946
+ if (subscriber.hasOwnProperty("stream") && subscriber.stream.streamId == stream.streamId) {
2947
+ subscriber._subscribeToAudio(newValue, true);
2948
+ break;
2949
+ }
2950
+ }
2951
+ } else if ("hasVideo" == changedProperty) {
2952
+ for (componentId in session.subscribers) {
2953
+ subscriber = session.subscribers[componentId];
2954
+ if (subscriber.hasOwnProperty("stream") && subscriber.stream.streamId == stream.streamId) {
2955
+ subscriber._subscribeToVideo(newValue, true);
2956
+ break;
2957
+ }
2958
+ }
2959
+ } else if ("orientation" == changedProperty) {
2960
+ for (componentId in session.subscribers) {
2961
+ subscriber = session.subscribers[componentId];
2962
+ if (subscriber.hasOwnProperty("stream") && subscriber.stream.streamId == stream.streamId) {
2963
+ subscriber.changeOrientation(newValue);
2964
+ break;
2965
+ }
2966
+ }
2967
+ } else if ("quality" == changedProperty) {
2968
+ //do nothing.
2969
+ } else {
2970
+ debug("Unknown property changed");
2971
+ }
2972
+ } catch(err) {
2973
+ var errorMsg = "TB.streamPropertyChangedHandler :: " + err;
2974
+ error(errorMsg);
2975
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2976
+ }
2977
+ },
2978
+
2979
+ microphoneLevelChangedHandler: function(sessionId, componentId, streamId, volume){
2980
+ //debug("TB.microphoneLevelChangedHandler: " + streamId);
2981
+ try {
2982
+ var session = this.sessions[sessionId];
2983
+
2984
+ if (!session) {
2985
+ var errorMsg = "TB.microphoneLevelChangedHandler :: Invalid session ID: " + sessionId;
2986
+ error(errorMsg);
2987
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2988
+ return;
2989
+ }
2990
+
2991
+ var subscriber = session.subscribers[componentId];
2992
+
2993
+ var event = new VolumeEvent(this.MICROPHONE_LEVEL_CHANGED, streamId, volume);
2994
+ session.dispatchEvent(event);
2995
+ } catch (err) {
2996
+ errorMsg = "microphoneLevelChanged :: " + err;
2997
+ error(errorMsg);
2998
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
2999
+ }
3000
+ },
3001
+
3002
+ connectionCreatedHandler: function(sessionId, connectionObjects, reason) {
3003
+ debug("TB.connectionCreated");
3004
+ try {
3005
+ var session = this.sessions[sessionId];
3006
+
3007
+ var connections = getConnections(connectionObjects);
3008
+ session.dispatchEvent(new ConnectionEvent(this.CONNECTION_CREATED, connections, reason));
3009
+ } catch(err) {
3010
+ var errorMsg = "TB.connectionCreated :: "+err;
3011
+ error(errorMsg);
3012
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3013
+ }
3014
+ },
3015
+
3016
+ connectionDestroyedHandler: function(sessionId, connectionObjects, reason) {
3017
+ debug("TB.connectionDestroyed");
3018
+ try {
3019
+ var session = this.sessions[sessionId];
3020
+
3021
+ var connections = getConnections(connectionObjects);
3022
+
3023
+ session.dispatchEvent(new ConnectionEvent(this.CONNECTION_DESTROYED, connections, reason));
3024
+ } catch(err) {
3025
+ var errorMsg = "TB.connectionDestroyed :: "+err;
3026
+ error(errorMsg);
3027
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3028
+ }
3029
+ },
3030
+
3031
+ signalHandler: function(sessionId, fromId) {
3032
+ debug("TB.signal");
3033
+ try {
3034
+ var session = this.sessions[sessionId];
3035
+
3036
+ session.dispatchEvent(new SignalEvent(this.SIGNAL_RECEIVED, getConnectionFromConnectionId(fromId)));
3037
+ } catch(err) {
3038
+ var errorMsg = "TB.signal ::"+err;
3039
+ error(errorMsg);
3040
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3041
+ }
3042
+ },
3043
+
3044
+ archiveCreatedHandler: function(sessionId, archive) {
3045
+ debug("TB.archiveCreatedHandler:" + sessionId + " - " + archive);
3046
+ try {
3047
+ var session = this.sessions[sessionId];
3048
+ var newArchive = getArchive(archive, sessionId);
3049
+ session.dispatchEvent(new ArchiveEvent(this.ARCHIVE_CREATED, [newArchive]));
3050
+ } catch(err) {
3051
+ var errorMsg = "TB.archiveCreatedHandler :: " + err;
3052
+ error(errorMsg);
3053
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3054
+ }
3055
+ },
3056
+
3057
+ archiveClosedHandler: function(sessionId, archive) {
3058
+ debug("TB.archiveClosedHandler:" + sessionId + " - " + archive.id);
3059
+ try {
3060
+ var session = this.sessions[sessionId];
3061
+ session.dispatchEvent(new ArchiveEvent(this.ARCHIVE_CLOSED, [createdArchives[sessionId][archive.id]]));
3062
+ delete createdArchives[sessionId][archive.id];
3063
+ } catch(err) {
3064
+ var errorMsg = "TB.archiveClosedHandler :: " + err;
3065
+ error(errorMsg);
3066
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3067
+ }
3068
+ },
3069
+
3070
+ archiveLoadedHandler: function(sessionId, archive) {
3071
+ debug("TB.archiveLoadedHandler:" + sessionId + " - " + archive.archiveId);
3072
+ try {
3073
+ var session = this.sessions[sessionId];
3074
+ var newArchive = new Archive(archive.id, archive.type, archive.title, sessionId);
3075
+ if (!loadedArchives.hasOwnProperty(sessionId)) loadedArchives[sessionId] = {};
3076
+ loadedArchives[sessionId][archive.id] = newArchive;
3077
+ session.dispatchEvent(new ArchiveEvent(this.ARCHIVE_LOADED, [newArchive]));
3078
+ } catch(err) {
3079
+ var errorMsg = "TB.archiveLoadedHandler :: " + err;
3080
+ error(errorMsg);
3081
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3082
+ }
3083
+ },
3084
+
3085
+ sessionRecordingStartedHandler: function(sessionId, archive) {
3086
+ debug("TB.sessionRecordingStartedHandler:" + sessionId + " - " + archive.id);
3087
+ try {
3088
+ var session = this.sessions[sessionId];
3089
+ session.dispatchEvent(new ArchiveEvent(this.SESSION_RECORDING_STARTED, [createdArchives[archive.id]]));
3090
+ } catch(err) {
3091
+ var errorMsg = "TB.sessionRecordingStartedHandler :: " + err;
3092
+ error(errorMsg);
3093
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3094
+ }
3095
+ },
3096
+
3097
+ sessionRecordingStoppedHandler: function(sessionId, archive) {
3098
+ debug("TB.sessionRecordingStoppedHandler:" + sessionId + " - " + archive);
3099
+ try {
3100
+ var session = this.sessions[sessionId];
3101
+ session.dispatchEvent(new ArchiveEvent(this.SESSION_RECORDING_STOPPED, [createdArchives[archive.id]]));
3102
+
3103
+ for (var pub in session.publishers) {
3104
+ if (session.publishers[pub].hasOwnProperty("id")) {
3105
+ var publisher = document.getElementById(session.publishers[pub].id);
3106
+ publisher.signalRecordingStopped();
3107
+ }
3108
+ };
3109
+ } catch(err) {
3110
+ var errorMsg = "TB.sessionRecordingStoppedHandler :: " + err;
3111
+ error(errorMsg);
3112
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3113
+ }
3114
+ },
3115
+
3116
+ sessionRecordingInProgressHandler: function(sessionId) {
3117
+ debug("TB.sessionRecordingInProgressHandler");
3118
+ try {
3119
+ var session = this.sessions[sessionId];
3120
+ session.dispatchEvent(new Event(this.SESSION_RECORDING_IN_PROGRESS, false));
3121
+ } catch(err) {
3122
+ var errorMsg = "TB.sessionRecordingStartedHandler :: " + err;
3123
+ error(errorMsg);
3124
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3125
+ }
3126
+ },
3127
+
3128
+ sessionNotRecordingHandler: function(sessionId) {
3129
+ debug("TB.sessionNotRecordingHandler");
3130
+ try {
3131
+ var session = this.sessions[sessionId];
3132
+ session.dispatchEvent(new Event(this.SESSION_NOT_RECORDING, false));
3133
+ } catch(err) {
3134
+ var errorMsg = "TB.sessionNotRecordingHandler :: " + err;
3135
+ error(errorMsg);
3136
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3137
+ }
3138
+ },
3139
+
3140
+ streamRecordingStartedHandler: function(sessionId, streamObjects) {
3141
+ debug("TB.streamRecordingStartedHandler:" + sessionId);
3142
+ try {
3143
+ var session = this.sessions[sessionId];
3144
+ var streams = getStreams(streamObjects, sessionId);
3145
+ session.dispatchEvent(new StreamEvent(this.STREAM_RECORDING_STARTED, streams, "", false));
3146
+
3147
+ for (var pub in session.publishers) {
3148
+ if (session.publishers[pub].hasOwnProperty("id")) {
3149
+ var publisher = document.getElementById(session.publishers[pub].id);
3150
+ for (var i=0; i < streamObjects.length; i++) {
3151
+ if (publisher.getStreamId() == streamObjects[i].streamId) {
3152
+ publisher.signalRecordingStarted();
3153
+ debug("TB.streamRecordingStartedHandler: signal: " + streamObjects[i].streamId);
3154
+ break;
3155
+ }
3156
+ };
3157
+ }
3158
+ };
3159
+ } catch(err) {
3160
+ var errorMsg = "TB.streamRecordingStartedHandler :: " + err;
3161
+ error(errorMsg);
3162
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3163
+ }
3164
+ },
3165
+
3166
+ streamRecordingStoppedHandler: function(sessionId, streamObjects) {
3167
+ debug("TB.streamRecordingStoppedHandler");
3168
+ try {
3169
+ var session = this.sessions[sessionId];
3170
+ var streams = getStreams(streamObjects, sessionId);
3171
+ session.dispatchEvent(new StreamEvent(this.STREAM_RECORDING_STOPPED, streams, "", false));
3172
+
3173
+ for (var pub in session.publishers) {
3174
+ if (session.publishers[pub].hasOwnProperty("id")) {
3175
+ var publisher = document.getElementById(session.publishers[pub].id);
3176
+
3177
+ for (var i=0; i < streamObjects.length; i++) {
3178
+ if (publisher.getStreamId() == streamObjects[i].streamId) {
3179
+ publisher.signalRecordingStopped();
3180
+ debug("TB.streamRecordingStoppedHandler: signal: " + streamObjects[i].streamId);
3181
+ break;
3182
+ }
3183
+ }
3184
+ }
3185
+ };
3186
+ } catch(err) {
3187
+ var errorMsg = "TB.streamRecordingStoppedHandler :: " + err;
3188
+ error(errorMsg);
3189
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3190
+ }
3191
+ },
3192
+
3193
+ streamRecordingInProgressHandler: function(sessionId, streamObjects) {
3194
+ debug("TB.streamRecordingInProgressHandler");
3195
+ try {
3196
+ var session = this.sessions[sessionId];
3197
+ var streams = getStreams(streamObjects, sessionId);
3198
+ session.dispatchEvent(new StreamEvent(this.STREAM_RECORDING_IN_PROGRESS, streams, "", false));
3199
+ } catch(err) {
3200
+ var errorMsg = "TB.streamRecordingInProgressHandler :: " + err;
3201
+ error(errorMsg);
3202
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3203
+ }
3204
+ },
3205
+
3206
+ playbackStartedHandler: function(sessionId, archive) {
3207
+ debug("TB.playbackStartedHandler");
3208
+ try {
3209
+ var session = this.sessions[sessionId];
3210
+ archiveObj = new Archive(archive.id, archive.type, archive.title, sessionId);
3211
+ session.dispatchEvent(new ArchiveEvent(this.PLAYBACK_STARTED, [archiveObj]));
3212
+ } catch(err) {
3213
+ var errorMsg = "TB.playbackStartedHandler :: " + err;
3214
+ error(errorMsg);
3215
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3216
+ }
3217
+ },
3218
+
3219
+ playbackStoppedHandler: function(sessionId, archive) {
3220
+ debug("TB.playbackStoppedHandler");
3221
+ try {
3222
+ var session = this.sessions[sessionId];
3223
+ archiveObj = new Archive(archive.id, archive.type, archive.title, sessionId);
3224
+ session.dispatchEvent(new ArchiveEvent(this.PLAYBACK_STOPPED, [archiveObj]));
3225
+ } catch(err) {
3226
+ var errorMsg = "TB.playbackStoppedHandler :: " + err;
3227
+ error(errorMsg);
3228
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3229
+ }
3230
+ },
3231
+
3232
+ // Group callbacks
3233
+ groupPropertiesUpdatedHandler: function(sessionId, groupObject) {
3234
+ debug("TB.groupPropertiesUpdated");
3235
+ try {
3236
+ var group = this.groups[sessionId + "_" + groupObject.groupId];
3237
+ if(!group) {
3238
+ error("TB.groupPropertiesUpdated :: Invalid group ID: " + sessionId + "_" + groupObject.groupId);
3239
+ return;
3240
+ }
3241
+
3242
+ group.dispatchEvent(new Event(this.GROUP_PROPERTIES_UPDATED));
3243
+ } catch(err) {
3244
+ var errorMsg ="TB.groupPropertiesUpdated :: " + err;
3245
+ error(errorMsg);
3246
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3247
+ }
3248
+ },
3249
+
3250
+ // Publisher or Subscriber callbacks
3251
+
3252
+ //publisher callbacks:
3253
+
3254
+ videoComponentLoadedHandler: function(sessionId, componentId) {
3255
+ try {
3256
+ if (sessionId) {
3257
+ var session = this.sessions[sessionId];
3258
+ if(!session) return;
3259
+
3260
+ var publisher = session.publishers[componentId];
3261
+ var subscriber = session.subscribers[componentId];
3262
+
3263
+ if (publisher && !publisher.loaded) {
3264
+ publisher.loaded = true;
3265
+ if (publisher.modified) {
3266
+ if (publisher.audioPublished != null) publisher.publishAudio(publisher.audioPublished);
3267
+ if (publisher.videoPublished != null) publisher.publishVideo(publisher.videoPublished);
3268
+ publisher.setMicrophoneGain(publisher.gain);
3269
+ publisher.setStyle(publisher._style);
3270
+ publisher.modified = false;
3271
+ }
3272
+ publisher.dispatchEvent(new Event("loaded"));
3273
+ }
3274
+
3275
+ if (subscriber && !subscriber.loaded) {
3276
+ subscriber.loaded = true;
3277
+ if (subscriber.modified) {
3278
+ if (subscriber.audioSubscribed != null) subscriber.subscribeToAudio(subscriber.audioSubscribed);
3279
+ if (subscriber.videoSubscribed != null) subscriber.subscribeToVideo(subscriber.videoSubscribed);
3280
+ subscriber.setAudioVolume(subscriber.audioVolume);
3281
+ subscriber.setStyle(subscriber._style);
3282
+ subscriber.modified = false;
3283
+ }
3284
+ subscriber.dispatchEvent(new Event("loaded"));
3285
+ }
3286
+ } else {
3287
+ var player = recorderManager.players[componentId];
3288
+ if (player && player._archiveId) {
3289
+ player.loadArchive(player._archiveId);
3290
+ player._archiveId = null;
3291
+ }
3292
+
3293
+ if (player && player._play) {
3294
+ player.play();
3295
+ player._play = false;
3296
+ }
3297
+
3298
+ var recorder = recorderManager.recorders[componentId];
3299
+ var component = player ? player : recorder;
3300
+ if (component && !component.loaded) {
3301
+ component.loaded = true;
3302
+ if (component.modified) {
3303
+ component.setStyle(component._style);
3304
+ if (component == recorder && component._title) {
3305
+ component.setTitle(_title);
3306
+ _title = "";
3307
+ }
3308
+ component.modified = false;
3309
+ }
3310
+ component.dispatchEvent(new Event("loaded"));
3311
+ }
3312
+
3313
+ }
3314
+ } catch (err) {
3315
+ var errorMsg = "videoComponentLoaded:: initialize component " + componentId + " - " + err;
3316
+ error(errorMsg);
3317
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3318
+ }
3319
+ },
3320
+
3321
+ // Used in resizing the publisher
3322
+ pubWidgetStyleHeightFrom: null,
3323
+ pubWidgetStyleWidthFrom: null,
3324
+
3325
+ // Publisher callbacks
3326
+ resizePublisherToTarget: function(sessionId, publisherId) {
3327
+ debug("TB.resize");
3328
+ try {
3329
+ var session = this.sessions[sessionId];
3330
+ if (!session) {
3331
+ var errorMsg = "TB.resize :: Invalid session ID: " + sessionId;
3332
+ error(errorMsg);
3333
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3334
+ return;
3335
+ }
3336
+
3337
+ var publisher = session.publishers[publisherId];
3338
+ if (!publisher) {
3339
+ errorMsg = "TB.resize :: Invalid publisher ID: " + publisherId;
3340
+ error(errorMsg);
3341
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3342
+ return;
3343
+ }
3344
+
3345
+ var pubWidget = document.getElementById(publisherId);
3346
+ if (!pubWidget) {
3347
+ errorMsg = "TB.resize :: Publisher " + publisherId + " does not exist in the DOM";
3348
+ error(errorMsg);
3349
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3350
+ return;
3351
+ }
3352
+
3353
+ var widthFrom = pubWidget.width;
3354
+ var heightFrom = pubWidget.height;
3355
+
3356
+ if (pubWidget.width != publisher.properties.width) {
3357
+ pubWidget.width = publisher.properties.width;
3358
+ }
3359
+
3360
+ if (pubWidget.height != publisher.properties.height) {
3361
+ pubWidget.height = publisher.properties.height;
3362
+ }
3363
+ if (pubWidget.style.height != pubWidgetStyleHeightFrom) {
3364
+ pubWidget.style.height = pubWidgetStyleHeightFrom;
3365
+ }
3366
+
3367
+ if (pubWidget.style.width != pubWidgetStyleWidthFrom) {
3368
+ pubWidget.style.width = pubWidgetStyleWidthFrom;
3369
+ }
3370
+
3371
+ var widthTo = pubWidget.width;
3372
+ var heightTo = pubWidget.height;
3373
+
3374
+ if (widthFrom != widthTo || heightFrom != heightTo) {
3375
+ // Only dispatch the resize event if we did resize
3376
+ publisher.dispatchEvent(new ResizeEvent(this.RESIZE, widthFrom, widthTo, heightFrom, heightTo));
3377
+ }
3378
+ } catch(err) {
3379
+ errorMsg = "resizePublisherToTarget :: Error resizing publisher - " + err;
3380
+ error(errorMsg);
3381
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3382
+ }
3383
+ },
3384
+
3385
+
3386
+ resizePublisherToShowSecurity: function(sessionId, publisherId, scaleFactor){
3387
+ debug("TB.resize");
3388
+ var session = this.sessions[sessionId];
3389
+ if (!session) {
3390
+ var errorMsg = "TB.resize :: Invalid session ID: " + sessionId;
3391
+ error(errorMsg);
3392
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3393
+ return;
3394
+ }
3395
+
3396
+ var publisher = session.publishers[publisherId];
3397
+ if (!publisher) {
3398
+ errorMsg = "TB.resize :: Invalid publisher ID: " + publisherId;
3399
+ error(errorMsg);
3400
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3401
+ return;
3402
+ }
3403
+
3404
+ var pubWidget = document.getElementById(publisherId);
3405
+ if (!pubWidget) {
3406
+ errorMsg = "TB.resize :: Publisher " + publisherId + " does not exist in the DOM";
3407
+ error(errorMsg);
3408
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3409
+ return;
3410
+ }
3411
+
3412
+ var widthFrom = publisher.properties.width = pubWidget.width;
3413
+ var heightFrom = publisher.properties.height = pubWidget.height;
3414
+
3415
+ pubWidgetStyleHeightFrom = pubWidget.style.height;
3416
+ pubWidgetStyleWidthFrom = pubWidget.style.width;
3417
+
3418
+ // The scaleFactor takes browser zoom into account
3419
+ var minWidth = MIN_ADOBE_WIDTH * scaleFactor;
3420
+ var minHeight = MIN_ADOBE_HEIGHT * scaleFactor;
3421
+
3422
+ if (pubWidget.width < minWidth) {
3423
+ pubWidget.width = minWidth;
3424
+ pubWidget.style.width = minWidth + "px";
3425
+ }
3426
+ if (pubWidget.height < minHeight) {
3427
+ pubWidget.height = minHeight;
3428
+ pubWidget.style.height = minHeight + "px";
3429
+ }
3430
+
3431
+ var widthTo = pubWidget.width;
3432
+ var heightTo = pubWidget.height;
3433
+ var styleTo = pubWidget.style;
3434
+
3435
+ if (widthFrom != widthTo || heightFrom != heightTo || pubWidgetStyleWidthFrom != styleTo.width || pubWidgetStyleHeightFrom != styleTo.height) {
3436
+ // Only dispatch the resize event if we did resize
3437
+ publisher.dispatchEvent(new ResizeEvent(this.RESIZE, widthFrom, widthTo, heightFrom, heightTo));
3438
+ }
3439
+
3440
+ },
3441
+
3442
+ settingsButtonClickHandler: function(sessionId, publisherId) {
3443
+ debug("TB.settingsButtonClick");
3444
+ try {
3445
+ var session = this.sessions[sessionId];
3446
+ if(!session) {
3447
+ var errorMsg = "TB.settingsButtonClick :: Invalid session ID: "+sessionId;
3448
+ error(errorMsg);
3449
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3450
+ return;
3451
+ }
3452
+
3453
+ var publisher = session.publishers[publisherId];
3454
+ if(!publisher) {
3455
+ errorMsg = "TB.settingsButtonClick :: Invalid publisher ID: "+publisherId;
3456
+ error(errorMsg);
3457
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3458
+ return;
3459
+ }
3460
+
3461
+ var event = new Event(this.SETTINGS_BUTTON_CLICK, true);
3462
+ publisher.dispatchEvent(event);
3463
+
3464
+ var defaultAction = function() {
3465
+ if (!event.isDefaultPrevented()) {
3466
+ var dm = TB.initDeviceManager(session.apiKey);
3467
+ dm.displayPanel(null, publisher, {});
3468
+ }
3469
+ };
3470
+
3471
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
3472
+ setTimeout(defaultAction, 2);
3473
+ } catch(err){
3474
+ errorMsg = "settingsButtonClick :: " + err;
3475
+ error(errorMsg);
3476
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3477
+ }
3478
+ },
3479
+
3480
+
3481
+
3482
+ recorderSettingsButtonClickHandler: function(recorderId) {
3483
+ debug("TB.recorderSettingsButtonClick");
3484
+ try {
3485
+ var recorder = recorderManager.recorders[recorderId];
3486
+ if(!recorder) {
3487
+ errorMsg = "TB.recorderSettingsButtonClick :: Invalid recorder ID: "+recorderId;
3488
+ error(errorMsg);
3489
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3490
+ return;
3491
+ }
3492
+
3493
+ var event = new Event(this.SETTINGS_BUTTON_CLICK, true);
3494
+ recorder.dispatchEvent(event);
3495
+
3496
+ var defaultAction = function() {
3497
+ if (!event.isDefaultPrevented()) {
3498
+ var dm = TB.initDeviceManager(recorderManager.apiKey);
3499
+ dm.displayPanel(null, recorder, {});
3500
+ }
3501
+ };
3502
+
3503
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
3504
+ setTimeout(defaultAction, 2);
3505
+ } catch(err){
3506
+ errorMsg = "recorderSettingsButtonClick :: " + err;
3507
+ error(errorMsg);
3508
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3509
+ }
3510
+ },
3511
+
3512
+ deviceAccessHandler: function(sessionId, publisherId, type) {
3513
+ debug("TB.deviceAccessHandler: " + type);
3514
+ try {
3515
+ var session = this.sessions[sessionId];
3516
+ if (!session) {
3517
+ var errorMsg = "TB.deviceAccessHandler :: Invalid session ID: " + sessionId;
3518
+ error(errorMsg);
3519
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3520
+ return;
3521
+ }
3522
+
3523
+ var publisher = session.publishers[publisherId];
3524
+ if (!publisher) {
3525
+ errorMsg = "TB.deviceAccessHandler :: Invalid publisher ID: " + publisherId;
3526
+ error(errorMsg);
3527
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3528
+ return;
3529
+ }
3530
+
3531
+ var event = new Event(type, true);
3532
+ publisher.dispatchEvent(event);
3533
+
3534
+ if (type == TB.ACCESS_DENIED) {
3535
+ var defaultAction = function() {
3536
+ var publisherElement = document.getElementById(publisher.id);
3537
+ if (publisherElement) {
3538
+ var parentNode = publisherElement.parentNode;
3539
+ session.unpublish(publisher);
3540
+
3541
+ var replaceElement = document.createElement("div");
3542
+ replaceElement.setAttribute("id", publisher.replacedDivId);
3543
+ parentNode.appendChild(replaceElement);
3544
+
3545
+ session._embedPublisher(publisher);
3546
+ }
3547
+ };
3548
+
3549
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
3550
+ setTimeout(defaultAction, 2);
3551
+ }
3552
+ } catch(err) {
3553
+ errorMsg = type + " :: " + err;
3554
+ error(errorMsg);
3555
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3556
+ }
3557
+ },
3558
+
3559
+ deviceInactiveHandler: function(sessionId, publisherId, camera, microphone){
3560
+ debug("TB.deviceInactiveHandler");
3561
+
3562
+ try {
3563
+ var session = this.sessions[sessionId];
3564
+
3565
+ if (!session) {
3566
+ var errorMsg = "TB.deviceInactiveHandler :: Invalid session ID: " + sessionId;
3567
+ error(errorMsg);
3568
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3569
+ return;
3570
+ }
3571
+
3572
+ var publisher = session.publishers[publisherId];
3573
+
3574
+ if (!publisher) {
3575
+ error("TB.deviceInactiveHandler :: Invalid publisher ID: " + publisherId);
3576
+ return;
3577
+ }
3578
+
3579
+ var event = new DeviceEvent(this.DEVICE_INACTIVE, camera, microphone);
3580
+ publisher.dispatchEvent(event);
3581
+ } catch (err) {
3582
+ errorMsg = "deviceInactive :: " + err;
3583
+ error(errorMsg);
3584
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3585
+ }
3586
+ },
3587
+
3588
+ echoCancellationModeChangedHandler: function(sessionId, publisherId, camera, microphone){
3589
+ debug("TB.echoCancellationModeChangedHandler");
3590
+
3591
+ try {
3592
+ var session = this.sessions[sessionId];
3593
+
3594
+ if (!session) {
3595
+ var errorMsg = "TB.echoCancellationModeChangedHandler :: Invalid session ID: " + sessionId;
3596
+ error(errorMsg);
3597
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3598
+ return;
3599
+ }
3600
+
3601
+ var publisher = session.publishers[publisherId];
3602
+
3603
+ if (!publisher) {
3604
+ error("TB.echoCancellationModeChangedHandler :: Invalid publisher ID: " + publisherId);
3605
+ return;
3606
+ }
3607
+
3608
+ var event = new DeviceEvent(this.ECHO_CANCELLATION_MODE_CHANGED, camera, microphone);
3609
+ publisher.dispatchEvent(event);
3610
+ } catch (err) {
3611
+ errorMsg = "echoCancellationModeChanged :: " + err;
3612
+ error(errorMsg);
3613
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3614
+ }
3615
+ },
3616
+
3617
+ // DeviceManager callbacks
3618
+ devicesDetectedHandler: function(cameraObjects, microphoneObjects, selectedCameraIndex, selectedMicrophoneIndex) {
3619
+ debug("TB.devicesDetected");
3620
+ try {
3621
+ var cameras = getCameras(cameraObjects);
3622
+ var microphones = getMicrophones(microphoneObjects);
3623
+
3624
+ deviceManager.dispatchEvent(new DeviceStatusEvent(this.DEVICES_DETECTED, cameras, microphones, cameras[selectedCameraIndex], microphones[selectedMicrophoneIndex]));
3625
+ setTimeout(function() { removeSWF(deviceDetectorId, "devicesDetectedHandler :: "); deviceDetectorId = null; }, 0); // must be asynchronous
3626
+ } catch(err) {
3627
+ var errorMsg = "devicesDetectedHandler :: " + err;
3628
+ error(errorMsg);
3629
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3630
+ }
3631
+ },
3632
+
3633
+ // DevicePanel callbacks
3634
+ devicesSelectedHandler: function(devicePanelId, camera, microphone) {
3635
+ debug("TB.devicesSelected");
3636
+ try {
3637
+ cameraSelected = true;
3638
+ var devicePanel = deviceManager.panels[devicePanelId];
3639
+ if(!devicePanel) {
3640
+ error("TB.devicesSelected :: Invalid DevicePanel ID: "+devicePanelId);
3641
+ return;
3642
+ }
3643
+
3644
+ if (devicePanel.component) {
3645
+ devicePanel.component.setCamera(camera);
3646
+ devicePanel.component.setMicrophone(microphone);
3647
+ }
3648
+
3649
+ devicePanel.dispatchEvent(new DeviceEvent(this.DEVICES_SELECTED, camera, microphone));
3650
+ } catch(err){
3651
+ var errorMsg = "devicesSelected :: " + err;
3652
+ error(errorMsg);
3653
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3654
+ }
3655
+ },
3656
+
3657
+ closeButtonClickHandler: function(devicePanelId) {
3658
+ debug("TB.closeButtonClick");
3659
+ try {
3660
+ var devicePanel = deviceManager.panels[devicePanelId];
3661
+ if(!devicePanel) {
3662
+ var errorMsg = "TB.devicesSelected :: Invalid DevicePanel ID: "+devicePanelId;
3663
+ error(errorMsg);
3664
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3665
+ return;
3666
+ }
3667
+
3668
+ var event = new Event(this.CLOSE_BUTTON_CLICK, true);
3669
+ devicePanel.dispatchEvent(event);
3670
+
3671
+ var defaultAction = function() {
3672
+ if (!event.isDefaultPrevented()) {
3673
+ deviceManager.removePanel(devicePanel);
3674
+ }
3675
+ };
3676
+
3677
+ // The event handler is called asynchronously after 1 millisecond. The default action happens after that.
3678
+ setTimeout(defaultAction, 2);
3679
+ } catch(err) {
3680
+ errorMsg = "closeButtonClick :: " + err;
3681
+ error(errorMsg);
3682
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3683
+ }
3684
+ },
3685
+
3686
+ // Player callbacks
3687
+ playerArchiveLoadedHandler: function(playerId) {
3688
+ debug("Player.archiveLoadedHandler");
3689
+ try {
3690
+ var player = recorderManager.players[playerId];
3691
+ player.dispatchEvent(new Event(this.ARCHIVE_LOADED));
3692
+ } catch(err) {
3693
+ var errorMsg = "Player.archiveLoadedHandler :: " + err;
3694
+ error(errorMsg);
3695
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3696
+ }
3697
+ },
3698
+
3699
+ playingStartedHandler: function(playerId) {
3700
+ debug("Player.playingHandler");
3701
+ try {
3702
+ var player = recorderManager.players[playerId];
3703
+ player.dispatchEvent(new Event(this.PLAYBACK_STARTED));
3704
+ } catch(err) {
3705
+ var errorMsg = "Player.playingStartedHandler :: " + err;
3706
+ error(errorMsg);
3707
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3708
+ }
3709
+ },
3710
+
3711
+ playingStoppedHandler: function(playerId, archive) {
3712
+ debug("Player.playingStoppedHandler");
3713
+ try {
3714
+ var player = recorderManager.players[playerId];
3715
+ player.dispatchEvent(new Event(this.PLAYBACK_STOPPED));
3716
+ } catch(err) {
3717
+ var errorMsg = "Player.playingStoppedHandler :: " + err;
3718
+ error(errorMsg);
3719
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3720
+ }
3721
+ },
3722
+
3723
+ // Recorder callbacks
3724
+ recordingStartedHandler: function(recorderId) {
3725
+ debug("Recorder.recordingStartedHandler");
3726
+ try {
3727
+ var recorder = recorderManager.recorders[recorderId];
3728
+ recorder.dispatchEvent(new Event(this.RECORDING_STARTED));
3729
+ } catch(err) {
3730
+ var errorMsg = "Recorder.recordingStartedHandler :: " + err;
3731
+ error(errorMsg);
3732
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3733
+ }
3734
+ },
3735
+
3736
+ recordingStoppedHandler: function(recorderId) {
3737
+ debug("Recorder.recordingStoppedHandler");
3738
+ try {
3739
+ var recorder = recorderManager.recorders[recorderId];
3740
+ recorder.dispatchEvent(new Event(this.RECORDING_STOPPED));
3741
+ } catch(err) {
3742
+ var errorMsg = "Recorder.recordingStoppedHandler :: " + err;
3743
+ error(errorMsg);
3744
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3745
+ }
3746
+ },
3747
+
3748
+ recorderPlaybackStartedHandler: function(recorderId) {
3749
+ debug("Recorder.playbackStartedddHandler");
3750
+ try {
3751
+ var recorder = recorderManager.recorders[recorderId];
3752
+ recorder.dispatchEvent(new Event(this.PLAYBACK_STARTED));
3753
+ } catch(err) {
3754
+ var errorMsg = "Recorder.playbackStartedHandler :: " + err;
3755
+ error(errorMsg);
3756
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3757
+ }
3758
+ },
3759
+
3760
+ recorderPlaybackStoppedHandler: function(recorderId) {
3761
+ debug("Recorder.playbackStoppedHandler");
3762
+ try {
3763
+ var recorder = recorderManager.recorders[recorderId];
3764
+ recorder.dispatchEvent(new Event(this.PLAYBACK_STOPPED));
3765
+ } catch(err) {
3766
+ var errorMsg = "Recorder.playbackStoppedHandler :: " + err;
3767
+ error(errorMsg);
3768
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3769
+ }
3770
+ },
3771
+
3772
+ archiveSavedHandler: function(recorderId, archive) {
3773
+ debug("Recorder.archiveSavedHandler");
3774
+ try {
3775
+ var recorder = recorderManager.recorders[recorderId];
3776
+ var newArchive = new Archive(archive.id, archive.type, archive.title);
3777
+ recorder.dispatchEvent(new ArchiveEvent(this.ARCHIVE_SAVED, [newArchive]));
3778
+ } catch(err) {
3779
+ var errorMsg = "Recorder.archiveSavedHandler :: " + err;
3780
+ error(errorMsg);
3781
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3782
+ }
3783
+ },
3784
+
3785
+ stateChangedHandler: function(sessionId, values) {
3786
+ debug("TB.stateChangeHandler");
3787
+ var session = this.sessions[sessionId];
3788
+ if(!session) {
3789
+ var errorMsg = "TB.stateChangedHandler :: Invalid session ID: "+sessionId;
3790
+ error(errorMsg);
3791
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3792
+ return;
3793
+ }
3794
+
3795
+ function getStateMgrForKey(key) {
3796
+ key = key.replace(/"/g, "");
3797
+ var match = key.match(/TB_archive_([^_]+)_(.*)/);
3798
+ if (match) {
3799
+ archiveId = match[1];
3800
+ key = match[2];
3801
+ var archive = loadedArchives[sessionId][archiveId];
3802
+ if (!archive) {
3803
+ var errorMsg = "Archive.startPlayback :: Archive not loaded.";
3804
+ error(errorMsg);
3805
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3806
+ return;
3807
+ }
3808
+
3809
+ return archive.getStateManager();
3810
+ }
3811
+
3812
+ return session.getStateManager();
3813
+ }
3814
+
3815
+ var changedEventDispatched = false;
3816
+ for (var key in values) {
3817
+ var stateMgr = getStateMgrForKey(key);
3818
+
3819
+ if (!changedEventDispatched) {
3820
+ stateMgr.dispatchEvent(new StateChangedEvent("changed", values));
3821
+ changedEventDispatched = true;
3822
+ }
3823
+
3824
+ var changedValues = {};
3825
+ changedValues[key] = values[key];
3826
+
3827
+ stateMgr.dispatchEvent(new StateChangedEvent("changed:" + key, changedValues));
3828
+ }
3829
+
3830
+ if (!changedEventDispatched) {
3831
+ session.getStateManager().dispatchEvent(new StateChangedEvent("changed", values));
3832
+ }
3833
+ },
3834
+
3835
+ stateChangedFailedHandler: function(sessionId, reasonCode, reason, failedValues){
3836
+ debug("TB.stateChangedFailedHandler");
3837
+ var session = this.sessions[sessionId];
3838
+ if(!session) {
3839
+ var errorMsg = "TB.stateChangedFailedHandler :: Invalid session ID: "+sessionId;
3840
+ error(errorMsg);
3841
+ TB.exceptionHandler(errorMsg, "Internal Error", 2000);
3842
+ return;
3843
+ }
3844
+
3845
+ var stateMgr = session.getStateManager();
3846
+ stateMgr.dispatchEvent(new ChangeFailedEvent("changeFailed", reasonCode, reason, failedValues));
3847
+ },
3848
+
3849
+ reportIssueHandler: function(issueId, showReport){
3850
+ debug("TB.reportIssue");
3851
+
3852
+ if (showReport == null) showReport = false;
3853
+ if (showingIssueForm) return;
3854
+
3855
+ // Setup form
3856
+ var form = document.createElement("form");
3857
+ form.setAttribute("action", "http://staging.tokbox.com/reportIssue.php");
3858
+ form.setAttribute("method", "post");
3859
+ form.setAttribute("target", "formresult");
3860
+
3861
+ createHiddenElement(form, "issueId", issueId);
3862
+
3863
+ // Add client info
3864
+ createHiddenElement(form, "userAgent", navigator.userAgent);
3865
+ createHiddenElement(form, "environment", "JS");
3866
+ var playerVersion = swfobject.getFlashPlayerVersion();
3867
+ createHiddenElement(form, "flashVersion", playerVersion.major + "." + playerVersion.minor + "." + playerVersion.release);
3868
+
3869
+ // Add JS Logs
3870
+ var jsLogs = window.opentokdebug.getLogs();
3871
+ createHiddenElement(form, "jsLogs", jsLogs);
3872
+
3873
+ var addedAPIKey = false;
3874
+
3875
+ var sessionCount = 0;
3876
+ for (var i in TB.sessions) {
3877
+ var session = TB.sessions[i];
3878
+ if (!session.hasOwnProperty("sessionId")) {
3879
+ continue;
3880
+ }
3881
+ if (!addedAPIKey) {
3882
+ createHiddenElement(form, "apiKey", session.apiKey);
3883
+ addedAPIKey = true;
3884
+ }
3885
+ var widgetCount = 0;
3886
+
3887
+ createHiddenElement(form, "session_" + ++sessionCount, session.sessionId);
3888
+ // Add controller logs
3889
+ var controllerId = "controller_" + session.sessionId;
3890
+ var controllerLogs = getDataForUIComponent(controllerId);
3891
+
3892
+ // This may be undefined if a session was initialized but never connected
3893
+ if(controllerLogs)
3894
+ createHiddenElement(form, "widget_" + session.sessionId + "_" + ++widgetCount, controllerLogs);
3895
+
3896
+ // Add subscriber logs
3897
+ if (session.hasOwnProperty("subscribers")) {
3898
+ for (var subscriber in session.subscribers) {
3899
+ if (session.subscribers[subscriber].hasOwnProperty("id")) {
3900
+ var subscriberLogs = getDataForUIComponent(session.subscribers[subscriber].id);
3901
+ createHiddenElement(form, "widget_" + session.sessionId + "_" + ++widgetCount, subscriberLogs);
3902
+ }
3903
+ }
3904
+ }
3905
+
3906
+ // Add publisher logs
3907
+ if (session.hasOwnProperty("publishers")) {
3908
+ var publisherCount = 0;
3909
+ for (var publisher in session.publishers) {
3910
+ if (session.publishers[publisher].hasOwnProperty("id")) {
3911
+ var publisherLogs = getDataForUIComponent(session.publishers[publisher].id);
3912
+ createHiddenElement(form, "widget_" + session.sessionId + "_" + ++widgetCount, publisherLogs);
3913
+ }
3914
+ }
3915
+ }
3916
+ }
3917
+
3918
+ if (!showReport) {
3919
+ var textField = document.createElement("textarea");
3920
+ textField.setAttribute("name", "description");
3921
+ textField.setAttribute("rows", 8);
3922
+ textField.setAttribute("cols", 40);
3923
+ textField.style.height = "110px";
3924
+ textField.style.width = "300px";
3925
+ textField.style.display = "block";
3926
+ textField.style.visibility = "visible";
3927
+ form.appendChild(textField);
3928
+
3929
+ var submitBtn = document.createElement("input");
3930
+ submitBtn.setAttribute("type", "submit");
3931
+ submitBtn.setAttribute("value", "Report Issue");
3932
+ submitBtn.style.display = "inline";
3933
+ submitBtn.style.visibility = "visible";
3934
+ form.appendChild(submitBtn);
3935
+
3936
+ var cancelBtn = document.createElement("input");
3937
+ cancelBtn.setAttribute("type", "button");
3938
+ cancelBtn.setAttribute("value", "Cancel");
3939
+ cancelBtn.style.display = "inline";
3940
+ cancelBtn.style.visibility = "visible";
3941
+ form.appendChild(cancelBtn);
3942
+
3943
+ var width = 390;
3944
+ var height = 242;
3945
+ var div = document.createElement("div");
3946
+ div.setAttribute('id', 'opentokReportIssue');
3947
+ div.style.position = "absolute";
3948
+ div.style.top = "25%";
3949
+ div.style.left = "50%";
3950
+ div.style.width = width + "px";
3951
+ div.style.height = height + "px";
3952
+ div.style.marginLeft = (0 - width/2) + "px";
3953
+ div.style.marginTop = (0 - height/4) + "px";
3954
+ div.style.paddingLeft = "32px";
3955
+ div.style.paddingRight = "15px";
3956
+ div.style.paddingTop = "15px";
3957
+ div.style.display = "block";
3958
+ div.style.visibility = "visible";
3959
+ div.style.lineHeight = "15px";
3960
+ div.style.zIndex = highZ() + 1;
3961
+
3962
+ div.innerHTML = "<span style=\"color:#4c4c4c;font-size:18px;display:inline;visibility:visible;\">We're sorry to hear that something went wrong.</span><br/><br/>Please help us to debug your issue by providing a description of what happened.";
3963
+ div.style.backgroundColor = "#F7F7F7";
3964
+ div.style.border = "1px solid #CCC";
3965
+ div.style.fontWeight = "normal";
3966
+ div.style.fontFamily = "'Lucida Grande', 'Trebuchet MS', sans-serif";
3967
+ div.style.color = "#4c4c4c";
3968
+ div.style.fontSize = "13px";
3969
+
3970
+ div.appendChild(form);
3971
+ document.body.appendChild(div);
3972
+
3973
+ showingIssueForm = true;
3974
+
3975
+ closeForm = function() {
3976
+ document.body.removeChild(div);
3977
+ showingIssueForm = false;
3978
+ };
3979
+
3980
+ cancelBtn.onclick = closeForm;
3981
+ }
3982
+
3983
+ form.onsubmit = function() {
3984
+ window.open('#', 'formresult', 'scrollbars=no,menubar=no,height=200,width=400,resizable=yes,toolbar=no,status=no');
3985
+ setTimeout(function() {closeForm();}, 1000);
3986
+ };
3987
+
3988
+ if (showReport) {
3989
+ createHiddenElement(form, "showReport", true);
3990
+ document.body.appendChild(form);
3991
+
3992
+ form.submit();
3993
+ }
3994
+ }
3995
+
3996
+ };
3997
+ }();
3998
+ /*!
3999
+ * SWFObject v2.2 <http://code.google.com/p/swfobject/>
4000
+ * is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
4001
+ *
4002
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
4003
+ * of this software and associated documentation files (the "Software"), to deal
4004
+ * in the Software without restriction, including without limitation the rights
4005
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4006
+ * copies of the Software, and to permit persons to whom the Software is
4007
+ * furnished to do so, subject to the following conditions:
4008
+ *
4009
+ * The above copyright notice and this permission notice shall be included in
4010
+ * all copies or substantial portions of the Software
4011
+ */
4012
+ 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;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){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<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";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<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}();/*!
4013
+ * JavaScript Debug - v0.4 - 6/22/2010
4014
+ * http://benalman.com/projects/javascript-debug-console-log/
4015
+ *
4016
+ * Copyright (c) 2010 "Cowboy" Ben Alman
4017
+ * Dual licensed under the MIT and GPL licenses.
4018
+ * http://benalman.com/about/license/
4019
+ *
4020
+ * Permission is hereby granted, free of charge, to any person
4021
+ * obtaining a copy of this software and associated documentation
4022
+ * files (the "Software"), to deal in the Software without
4023
+ * restriction, including without limitation the rights to use,
4024
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
4025
+ * copies of the Software, and to permit persons to whom the
4026
+ * Software is furnished to do so, subject to the following
4027
+ * conditions:
4028
+ *
4029
+ * The above copyright notice and this permission notice shall be
4030
+ * included in all copies or substantial portions of the Software.
4031
+ */
4032
+
4033
+ // Script: JavaScript Debug: A simple wrapper for console.log
4034
+ //
4035
+ // *Version: 0.4, Last Updated: 6/22/2010*
4036
+ //
4037
+ // Tested with Internet Explorer 6-8, Firefox 3-3.6, Safari 3-4, Chrome 3-5, Opera 9.6-10.5
4038
+ //
4039
+ // Home - http://benalman.com/projects/javascript-debug-console-log/
4040
+ // GitHub - http://github.com/cowboy/javascript-debug/
4041
+ // Source - http://github.com/cowboy/javascript-debug/raw/master/ba-debug.js
4042
+ // (Minified) - http://github.com/cowboy/javascript-debug/raw/master/ba-debug.min.js (1.1kb)
4043
+ //
4044
+ // About: License
4045
+ //
4046
+ // Copyright (c) 2010 "Cowboy" Ben Alman,
4047
+ // Dual licensed under the MIT and GPL licenses.
4048
+ // http://benalman.com/about/license/
4049
+ //
4050
+ // About: Support and Testing
4051
+ //
4052
+ // Information about what browsers this code has been tested in.
4053
+ //
4054
+ // Browsers Tested - Internet Explorer 6-8, Firefox 3-3.6, Safari 3-4, Chrome
4055
+ // 3-5, Opera 9.6-10.5
4056
+ //
4057
+ // About: Examples
4058
+ //
4059
+ // These working examples, complete with fully commented code, illustrate a few
4060
+ // ways in which this plugin can be used.
4061
+ //
4062
+ // Examples - http://benalman.com/code/projects/javascript-debug/examples/debug/
4063
+ //
4064
+ // About: Revision History
4065
+ //
4066
+ // 0.4 - (6/22/2010) Added missing passthrough methods: exception,
4067
+ // groupCollapsed, table
4068
+ // 0.3 - (6/8/2009) Initial release
4069
+ //
4070
+ // Topic: Pass-through console methods
4071
+ //
4072
+ // assert, clear, count, dir, dirxml, exception, group, groupCollapsed,
4073
+ // groupEnd, profile, profileEnd, table, time, timeEnd, trace
4074
+ //
4075
+ // These console methods are passed through (but only if both the console and
4076
+ // the method exists), so use them without fear of reprisal. Note that these
4077
+ // methods will not be passed through if the logging level is set to 0 via
4078
+ // <debug.setLevel>.
4079
+
4080
+ window.opentokdebug = (function(){
4081
+ var window = this,
4082
+
4083
+ // Some convenient shortcuts.
4084
+ aps = Array.prototype.slice,
4085
+ con = window.console,
4086
+
4087
+ // Public object to be returned.
4088
+ that = {},
4089
+
4090
+ callback_func, callback_force,
4091
+
4092
+ // OpenTok has a default of no logging.
4093
+ log_level = 0,
4094
+
4095
+ // Logging methods, in "priority order". Not all console implementations
4096
+ // will utilize these, but they will be used in the callback passed to
4097
+ // setCallback.
4098
+ log_methods = ['error', 'warn', 'info', 'debug', 'log'],
4099
+
4100
+ // Pass these methods through to the console if they exist, otherwise just
4101
+ // fail gracefully. These methods are provided for convenience.
4102
+ pass_methods = 'assert clear count dir dirxml exception group groupCollapsed groupEnd profile profileEnd table time timeEnd trace'.split(' '),
4103
+ idx = pass_methods.length,
4104
+
4105
+ // Logs are stored here so that they can be recalled as necessary.
4106
+ logs = [];
4107
+
4108
+ while (--idx >= 0) {
4109
+ (function(method) {
4110
+
4111
+ // Generate pass-through methods. These methods will be called, if they
4112
+ // exist, as long as the logging level is non-zero.
4113
+ that[method] = function() {
4114
+ log_level !== 0 && con && con[method] && con[method].apply(con, arguments);
4115
+ };
4116
+
4117
+ })(pass_methods[idx]);
4118
+ }
4119
+
4120
+ idx = log_methods.length;
4121
+ while (--idx >= 0) {
4122
+ (function(idx, level) {
4123
+
4124
+ // Method: debug.log
4125
+ //
4126
+ // Call the console.log method if available. Adds an entry into the logs
4127
+ // array for a callback specified via <debug.setCallback>.
4128
+ //
4129
+ // Usage:
4130
+ //
4131
+ // debug.log( object [, object, ...] ); - -
4132
+ //
4133
+ // Arguments:
4134
+ //
4135
+ // object - (Object) Any valid JavaScript object.
4136
+ // Method: debug.debug
4137
+ //
4138
+ // Call the console.debug method if available, otherwise call console.log.
4139
+ // Adds an entry into the logs array for a callback specified via
4140
+ // <debug.setCallback>.
4141
+ //
4142
+ // Usage:
4143
+ //
4144
+ // debug.debug( object [, object, ...] ); - -
4145
+ //
4146
+ // Arguments:
4147
+ //
4148
+ // object - (Object) Any valid JavaScript object.
4149
+ // Method: debug.info
4150
+ //
4151
+ // Call the console.info method if available, otherwise call console.log.
4152
+ // Adds an entry into the logs array for a callback specified via
4153
+ // <debug.setCallback>.
4154
+ //
4155
+ // Usage:
4156
+ //
4157
+ // debug.info( object [, object, ...] ); - -
4158
+ //
4159
+ // Arguments:
4160
+ //
4161
+ // object - (Object) Any valid JavaScript object.
4162
+ // Method: debug.warn
4163
+ //
4164
+ // Call the console.warn method if available, otherwise call console.log.
4165
+ // Adds an entry into the logs array for a callback specified via
4166
+ // <debug.setCallback>.
4167
+ //
4168
+ // Usage:
4169
+ //
4170
+ // debug.warn( object [, object, ...] ); - -
4171
+ //
4172
+ // Arguments:
4173
+ //
4174
+ // object - (Object) Any valid JavaScript object.
4175
+ // Method: debug.error
4176
+ //
4177
+ // Call the console.error method if available, otherwise call console.log.
4178
+ // Adds an entry into the logs array for a callback specified via
4179
+ // <debug.setCallback>.
4180
+ //
4181
+ // Usage:
4182
+ //
4183
+ // debug.error( object [, object, ...] ); - -
4184
+ //
4185
+ // Arguments:
4186
+ //
4187
+ // object - (Object) Any valid JavaScript object.
4188
+ that[level] = function() {
4189
+ var args = aps.call(arguments),
4190
+ log_arr = [level].concat(args);
4191
+
4192
+ logs.push(log_arr);
4193
+
4194
+ if (!con || !is_level(idx)) {
4195
+ return;
4196
+ }
4197
+ exec_callback(log_arr); // OpenTok executes callback only if the proper level
4198
+ // OpenTok - this is a fix for firebug 1.6.0 submitted by someone else. hopefully it'll get incorporated into
4199
+ // the next official release and it can be removed.
4200
+ (con.firebug || window.Firebug) ? con[level].apply(con, args) : con[level] ? con[level](args) : con.log(args);
4201
+ };
4202
+
4203
+ })(idx, log_methods[idx]);
4204
+ }
4205
+
4206
+ // Execute the callback function if set.
4207
+ function exec_callback(args) {
4208
+ if (callback_func && (callback_force || !con || !con.log)) {
4209
+ callback_func.apply(window, args);
4210
+ }
4211
+ };
4212
+
4213
+ // Method: debug.setLevel
4214
+ //
4215
+ // Set a minimum or maximum logging level for the console. Doesn't affect
4216
+ // the <debug.setCallback> callback function, but if set to 0 to disable
4217
+ // logging, <Pass-through console methods> will be disabled as well.
4218
+ //
4219
+ // Usage:
4220
+ //
4221
+ // debug.setLevel( [ level ] ) - -
4222
+ //
4223
+ // Arguments:
4224
+ //
4225
+ // level - (Number) If 0, disables logging. If negative, shows N lowest
4226
+ // priority levels of log messages. If positive, shows N highest priority
4227
+ // levels of log messages.
4228
+ //
4229
+ // Priority levels:
4230
+ //
4231
+ // log (1) < debug (2) < info (3) < warn (4) < error (5)
4232
+ that.setLevel = function(level) {
4233
+ log_level = typeof level === 'number' ? level : 9;
4234
+ };
4235
+
4236
+ // Determine if the level is visible given the current log_level.
4237
+ function is_level(level) {
4238
+ return log_level > 0 ? log_level > level : log_methods.length + log_level <= level;
4239
+ };
4240
+
4241
+ // Method: debug.setCallback
4242
+ //
4243
+ // Set a callback to be used if logging isn't possible due to console.log
4244
+ // not existing. If unlogged logs exist when callback is set, they will all
4245
+ // be logged immediately unless a limit is specified.
4246
+ //
4247
+ // Usage:
4248
+ //
4249
+ // debug.setCallback( callback [, force ] [, limit ] )
4250
+ //
4251
+ // Arguments:
4252
+ //
4253
+ // callback - (Function) The aforementioned callback function. The first
4254
+ // argument is the logging level, and all subsequent arguments are those
4255
+ // passed to the initial debug logging method.
4256
+ // force - (Boolean) If false, log to console.log if available, otherwise
4257
+ // callback. If true, log to both console.log and callback.
4258
+ // limit - (Number) If specified, number of lines to limit initial scrollback
4259
+ // to.
4260
+ that.setCallback = function() {
4261
+ var args = aps.call(arguments),
4262
+ max = logs.length,
4263
+ i = max;
4264
+
4265
+ callback_func = args.shift() || null;
4266
+ callback_force = typeof args[0] === 'boolean' ? args.shift() : false;
4267
+
4268
+ i -= typeof args[0] === 'number' ? args.shift() : max;
4269
+
4270
+ while (i < max) {
4271
+ exec_callback(logs[i++]);
4272
+ }
4273
+ };
4274
+
4275
+ that.getLogs = function() {
4276
+ return logs.join('\n');
4277
+ };
4278
+
4279
+ return that;
4280
+ })();// Add missing IE methods
4281
+
4282
+ // This was taken from:
4283
+ // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
4284
+ if (!Object.keys) Object.keys = function(o) {
4285
+ if (o !== Object(o)) throw new TypeError('Object.keys called on non-object');
4286
+ var ret = [],
4287
+ p;
4288
+ for (p in o) {
4289
+ if (Object.prototype.hasOwnProperty.call(o, p)) {
4290
+ ret.push(p);
4291
+ }
4292
+ }
4293
+ return ret;
4294
+ };
4295
+
4296
+ // This was taken from:
4297
+ // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
4298
+ if (!Array.prototype.indexOf) {
4299
+ Array.prototype.indexOf = function(searchElement) {
4300
+ "use strict";
4301
+ if (this === void 0 || this === null) {
4302
+ throw new TypeError();
4303
+ }
4304
+ var t = Object(this);
4305
+ var len = t.length >>> 0;
4306
+ if (len === 0) {
4307
+ return -1;
4308
+ }
4309
+ var n = 0;
4310
+ if (arguments.length > 0) {
4311
+ n = Number(arguments[1]);
4312
+ if (n !== n) { // shortcut for verifying if it's NaN
4313
+ n = 0;
4314
+ } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
4315
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
4316
+ }
4317
+ }
4318
+ if (n >= len) {
4319
+ return -1;
4320
+ }
4321
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
4322
+ for (; k < len; k++) {
4323
+ if (k in t && t[k] === searchElement) {
4324
+ return k;
4325
+ }
4326
+ }
4327
+ return -1;
4328
+ };
4329
+ }