social_stream 0.21.4 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/README.rdoc +11 -5
  2. data/base/app/assets/stylesheets/cheesecake.css.scss +1 -0
  3. data/base/app/views/cheesecake/_cheesecake.html.erb +3 -0
  4. data/base/app/views/cheesecake/_index.html.erb +88 -41
  5. data/base/app/views/cheesecake/_sector_form.html.erb +12 -12
  6. data/base/lib/social_stream/base/version.rb +1 -1
  7. data/base/lib/social_stream/test_helpers/controllers.rb +19 -2
  8. data/base/lib/tasks/db/populate.rake +190 -165
  9. data/base/social_stream-base.gemspec +1 -1
  10. data/base/spec/controllers/posts_controller_spec.rb +19 -1
  11. data/lib/social_stream/version.rb +1 -1
  12. data/presence/app/assets/images/status/chat.png +0 -0
  13. data/presence/app/assets/javascripts/chat_interface_manager.js.erb +42 -45
  14. data/presence/app/assets/javascripts/chat_parser.js +5 -5
  15. data/presence/app/assets/javascripts/chat_persistence.js +25 -26
  16. data/presence/app/assets/javascripts/chat_utilities.js +15 -11
  17. data/presence/app/assets/javascripts/chat_window_manager.js +129 -26
  18. data/presence/app/assets/javascripts/jquery.ui.chatbox.sstreampresence.js +22 -3
  19. data/presence/app/assets/javascripts/social_stream-presence.js +1 -0
  20. data/presence/app/assets/javascripts/videochat.js.erb +459 -0
  21. data/presence/app/assets/javascripts/xmpp_client_management.js.erb +303 -65
  22. data/presence/app/assets/stylesheets/chat.css.scss +42 -1
  23. data/presence/app/controllers/xmpp_controller.rb +20 -3
  24. data/presence/app/views/chat/_index.html.erb +7 -2
  25. data/presence/app/views/xmpp/getOpenTokSessionIDAndToken.xml.builder +6 -0
  26. data/presence/config/locales/en.yml +22 -1
  27. data/presence/config/locales/es.yml +23 -2
  28. data/presence/config/routes.rb +2 -0
  29. data/presence/ejabberd/conf/ssconfig_example.cfg +4 -3
  30. data/presence/ejabberd/ejabberd_files.zip +0 -0
  31. data/presence/ejabberd/ejabberd_scripts/authentication_script +22 -12
  32. data/presence/ejabberd/ejabberd_scripts/development_scripts/show_config.sh +9 -10
  33. data/presence/ejabberd/ejabberd_scripts/emanagement +275 -178
  34. data/presence/ejabberd/ejabberd_scripts/manageWebDomains +164 -0
  35. data/presence/ejabberd/ejabberd_scripts/rest_api_client_script +75 -32
  36. data/presence/ejabberd/ejabberd_scripts/synchronize_presence_script +81 -34
  37. data/presence/ejabberd/mod_sspresence/mod_sspresence.beam +0 -0
  38. data/presence/ejabberd/mod_sspresence/mod_sspresence.erl +27 -23
  39. data/presence/lib/OpenTok/Exceptions.rb +11 -0
  40. data/presence/lib/OpenTok/OpenTokSDK.rb +184 -0
  41. data/presence/lib/OpenTok/Session.rb +27 -0
  42. data/presence/lib/generators/social_stream/presence/templates/initializer.rb +8 -2
  43. data/presence/lib/open_tok.rb +31 -0
  44. data/presence/lib/opentok.rb +29 -0
  45. data/presence/lib/social_stream-presence.rb +4 -0
  46. data/presence/lib/social_stream/presence/models/buddy_manager.rb +1 -1
  47. data/presence/lib/social_stream/presence/version.rb +1 -1
  48. data/presence/lib/social_stream/presence/xmpp_server_order.rb +96 -76
  49. data/presence/lib/social_stream/presence/xmpp_ssclient.rb +54 -0
  50. data/presence/lib/tasks/presence/multidomain.rake +45 -0
  51. data/presence/lib/tasks/presence/synchronize.rake +18 -4
  52. data/presence/vendor/assets/javascripts/TB.min.js +4329 -0
  53. data/social_stream.gemspec +2 -2
  54. metadata +40 -30
  55. data/presence/ejabberd/ejabberd_scripts/reset_connection_script +0 -300
  56. data/presence/ejabberd/ejabberd_scripts/set_script_header.sh +0 -112
@@ -1,8 +1,3 @@
1
- require 'xmpp4r'
2
- require 'xmpp4r/muc'
3
- require 'xmpp4r/roster'
4
- require 'xmpp4r/client'
5
- require 'xmpp4r/message'
6
1
  require 'net/ssh'
7
2
  require 'net/sftp'
8
3
 
@@ -32,54 +27,53 @@ module SocialStream
32
27
  executeEmanagementCommand("removeBuddyFromRoster",[userSid,buddySid])
33
28
  end
34
29
 
35
-
36
-
37
- def synchronizePresence
30
+
31
+ # Presence synchronization
32
+ def synchronizePresence(webDomain)
38
33
  if isEjabberdNodeUp
39
- output = executeEmanagementCommand("getConnectedUsers",[])
40
- user_slugs = output.split("\n")
41
- synchronizePresenceForSlugs(user_slugs)
34
+ if (webDomain=="all")
35
+ output = executeEmanagementCommand("getConnectedJids",[])
36
+ else
37
+ output = executeEmanagementCommand("getConnectedJidsByDomain",[webDomain])
38
+ end
39
+ user_jids = output.split("\n")
40
+ synchronizePresenceForJids(user_jids)
42
41
  else
43
42
  resetPresence
44
43
  return "Xmpp Server Down: Reset Connected Users"
45
44
  end
46
45
  end
47
46
 
48
-
49
- def removeAllRosters
50
- executeEmanagementCommand("removeAllRosters",[])
47
+ def synchronizePresenceForJids(user_jids)
48
+ domains = getDomainsFromJids(user_jids)
49
+ domains.each do |domain|
50
+ user_slugs = getSlugsFromJids(user_jids,domain)
51
+ synchronizePresenceForSlugs(user_slugs,domain)
52
+ end
51
53
  end
52
-
53
-
54
- def synchronizeRosters
55
- commands = []
56
-
57
- #"Remove all rosters"
58
- commands << buildCommand("emanagement","removeAllRosters",[])
59
54
 
60
- #"Populate rosters"
61
- users = User.all
62
- checkedUsers = []
63
- site_name = I18n.t('site.name').delete(' ')
64
-
65
- users.each do |user|
66
- checkedUsers << user.slug
67
- contacts = user.contact_actors(:type=>:user)
68
- contacts.each do |contact|
69
- unless checkedUsers.include?(contact.slug)
70
- domain = SocialStream::Presence.domain
71
- user_sid = user.slug + "@" + domain
72
- contact_sid = contact.slug + "@" + domain
73
- commands << buildCommand("emanagement","setBidireccionalBuddys",[user_sid,contact_sid,user.name,contact.name,site_name,site_name])
55
+ def getSlugsFromJids(user_jids,domain)
56
+ user_slugs = []
57
+ user_jids.each do |user_jid|
58
+ if(user_jid.split("@")[1]==domain)
59
+ user_slugs << user_jid.split("@")[0]
74
60
  end
75
- end
76
- end
77
-
78
- executeCommands(commands)
61
+ end
62
+ return user_slugs
79
63
  end
80
64
 
81
-
82
- def synchronizePresenceForSlugs(user_slugs)
65
+ def getDomainsFromJids(user_jids)
66
+ domains = []
67
+ user_jids.each do |user_jid|
68
+ domain=user_jid.split("@")[1]
69
+ if !domains.include?(domain)
70
+ domains << domain
71
+ end
72
+ end
73
+ return domains
74
+ end
75
+
76
+ def synchronizePresenceForSlugs(user_slugs,domain)
83
77
 
84
78
  #Check connected users
85
79
  users = User.find_all_by_connected(true)
@@ -101,6 +95,7 @@ module SocialStream
101
95
  end
102
96
 
103
97
 
98
+ #Reset presence for all domains
104
99
  def resetPresence
105
100
  users = User.find_all_by_connected(true)
106
101
 
@@ -111,6 +106,40 @@ module SocialStream
111
106
  end
112
107
 
113
108
 
109
+
110
+ # Rosters synchronization
111
+ def removeAllRosters(webDomain)
112
+ executeEmanagementCommand("removeAllRostersByDomain",[webDomain])
113
+ end
114
+
115
+
116
+ def synchronizeRosters(webDomain)
117
+ commands = []
118
+
119
+ #"Remove all rosters"
120
+ commands << buildCommand("emanagement","removeAllRostersByDomain",[webDomain])
121
+
122
+ #"Populate rosters"
123
+ users = User.all
124
+ checkedUsers = []
125
+ site_name = I18n.t('site.name').delete(' ')
126
+
127
+ users.each do |user|
128
+ checkedUsers << user.slug
129
+ contacts = user.contact_actors(:type=>:user)
130
+ contacts.each do |contact|
131
+ unless checkedUsers.include?(contact.slug)
132
+ user_sid = user.slug + "@" + webDomain
133
+ contact_sid = contact.slug + "@" + webDomain
134
+ commands << buildCommand("emanagement","setBidireccionalBuddys",[user_sid,contact_sid,user.name,contact.name,site_name,site_name])
135
+ end
136
+ end
137
+ end
138
+
139
+ executeCommands(commands)
140
+ end
141
+
142
+
114
143
  #Installation methods
115
144
 
116
145
  def copyFolderToXmppServer(oPath,dPath)
@@ -183,8 +212,8 @@ module SocialStream
183
212
  autoconf.push("scripts_path=" + SocialStream::Presence.scripts_path)
184
213
  autoconf.push("ejabberd_password=" + SocialStream::Presence.xmpp_server_password)
185
214
  autoconf.push("secure_rest_api=" + SocialStream::Presence.secure_rest_api.to_s)
186
- autoconf.push("server_domain=" + SocialStream::Presence.domain)
187
215
  autoconf.push("cookie_name=" + Rails.application.config.session_options[:key])
216
+ autoconf.push("web_domains=[" + SocialStream::Presence.domain + "]")
188
217
 
189
218
  #Param options
190
219
  if options
@@ -337,7 +366,7 @@ module SocialStream
337
366
  output=""
338
367
  commands.each do |command|
339
368
  response = %x[#{command}]
340
- output = output + "\n" + response;
369
+ output = output + response + "\n";
341
370
  end
342
371
  return output
343
372
  end
@@ -354,7 +383,7 @@ module SocialStream
354
383
  commands.each do |command|
355
384
  response = session.exec!(command)
356
385
  if response != nil
357
- output = output + "\n" + response
386
+ output = output + response + "\n";
358
387
  end
359
388
  end
360
389
  end
@@ -506,43 +535,34 @@ module SocialStream
506
535
  return hash
507
536
  end
508
537
 
509
- #Xmpp client manage methods
510
538
 
511
- def getSocialStreamUserSid
512
- #XMPP DOMAIN
513
- domain = SocialStream::Presence.domain
514
- #SS Username
515
- ss_name = SocialStream::Presence.social_stream_presence_username
516
- return ss_name + "@" + domain
517
- end
518
-
519
-
520
- def openXmppClientForSocialStreamUser
521
- begin
522
- password= SocialStream::Presence.password
523
- client = Jabber::Client.new(Jabber::JID.new(getSocialStreamUserSid))
524
- client.connect
525
- client.auth(password)
526
- return client
527
- rescue Exception => e
528
- case e
529
- when Errno::ECONNREFUSED
530
- puts "Connection to XMPP Server refused"
531
- return nil
532
- else
533
- puts "Unknown exception: #{e.to_s}"
534
- return nil
535
- end
539
+ #Multidomain tasks
540
+ def addWebDomain(domain,url)
541
+ commands = []
542
+ if url
543
+ commands << buildCommand("manageWebDomains","add",[domain,url])
544
+ else
545
+ commands << buildCommand("manageWebDomains","add",[domain])
536
546
  end
547
+ return executeCommands(commands)
537
548
  end
538
549
 
539
-
540
- def sendXmppChatMessage(client,dest_sid,body)
541
- msg = Jabber::Message::new(dest_sid, body)
542
- msg.type=:chat
543
- client.send(msg)
550
+ def removeWebDomain(domain)
551
+ commands = []
552
+ commands << buildCommand("manageWebDomains","remove",[domain])
553
+ return executeCommands(commands)
544
554
  end
545
-
555
+
556
+ def updateWebDomain(domain,url)
557
+ commands = []
558
+ if url
559
+ commands << buildCommand("manageWebDomains","update",[domain,url])
560
+ else
561
+ commands << buildCommand("manageWebDomains","update",[domain])
562
+ end
563
+ return executeCommands(commands)
564
+ end
565
+
546
566
  end
547
567
  end
548
568
  end
@@ -0,0 +1,54 @@
1
+ require 'xmpp4r'
2
+ require 'xmpp4r/muc'
3
+ require 'xmpp4r/roster'
4
+ require 'xmpp4r/client'
5
+ require 'xmpp4r/message'
6
+
7
+
8
+ module SocialStream
9
+ module Presence
10
+ class XmppSsclient
11
+
12
+ class << self
13
+
14
+ #Xmpp client manage methods
15
+
16
+ def getSocialStreamUserSid
17
+ #WEB DOMAIN
18
+ domain = SocialStream::Presence.domain
19
+ #SS Username
20
+ ss_name = SocialStream::Presence.social_stream_presence_username
21
+ return ss_name + "@" + domain
22
+ end
23
+
24
+
25
+ def openXmppClientForSocialStreamUser
26
+ begin
27
+ password= SocialStream::Presence.password
28
+ client = Jabber::Client.new(Jabber::JID.new(getSocialStreamUserSid))
29
+ client.connect
30
+ client.auth(password)
31
+ return client
32
+ rescue Exception => e
33
+ case e
34
+ when Errno::ECONNREFUSED
35
+ puts "Connection to XMPP Server refused"
36
+ return nil
37
+ else
38
+ puts "Unknown exception: #{e.to_s}"
39
+ return nil
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ def sendXmppChatMessage(client,dest_sid,body)
46
+ msg = Jabber::Message::new(dest_sid, body)
47
+ msg.type=:chat
48
+ client.send(msg)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,45 @@
1
+ namespace :presence do
2
+ desc 'Add web domains to Xmpp Server'
3
+ task :multidomain => [ 'presence:multidomain:add', 'presence:multidomain:remove' ,
4
+ 'presence:multidomain:update']
5
+
6
+ namespace :multidomain do
7
+
8
+ desc "Add new web domain to XMPP Server"
9
+ task :add, [:domain, :url] => :environment do |t, args|
10
+ puts "Starting presence:multidomain:add"
11
+ unless args[:domain]
12
+ puts "Please specify a web domain"
13
+ puts "Syntax: rake presence:multidomain:add[domain,[url]]"
14
+ return
15
+ end
16
+ response = SocialStream::Presence::XmppServerOrder::addWebDomain(args[:domain],args[:url])
17
+ puts response
18
+ end
19
+
20
+ desc "Remove web domain from the XMPP Server"
21
+ task :remove, [:domain] => :environment do |t, args|
22
+ puts "Starting presence:multidomain:remove"
23
+ unless args[:domain]
24
+ puts "Please specify a web domain"
25
+ puts "Syntax: rake presence:multidomain:remove[domain]"
26
+ return
27
+ end
28
+ response = SocialStream::Presence::XmppServerOrder::removeWebDomain(args[:domain])
29
+ puts response
30
+ end
31
+
32
+ desc "Update web domain of XMPP Server"
33
+ task :update, [:domain, :url] => :environment do |t, args|
34
+ puts "Starting presence:multidomain:update"
35
+ unless args[:domain]
36
+ puts "Please specify a web domain"
37
+ puts "Syntax: rake presence:multidomain:update[domain,[url]]"
38
+ return
39
+ end
40
+ response = SocialStream::Presence::XmppServerOrder::updateWebDomain(args[:domain],args[:url])
41
+ puts response
42
+ end
43
+
44
+ end
45
+ end
@@ -5,17 +5,31 @@ namespace :presence do
5
5
  namespace :synchronize do
6
6
 
7
7
  desc "Synchronize user presence"
8
- task :connections => :environment do
8
+ task :connections, [:domain] => :environment do |t, args|
9
9
  puts "Starting presence:synchronize:connections"
10
- SocialStream::Presence::XmppServerOrder::synchronizePresence
10
+ unless args[:domain]
11
+ puts "No web domain specified"
12
+ domain = SocialStream::Presence.domain
13
+ puts "Executing rake presence:synchronize:connections[" + domain + "]"
14
+ else
15
+ domain = args[:domain]
16
+ end
17
+ SocialStream::Presence::XmppServerOrder::synchronizePresence(domain)
11
18
  puts "Synchronization complete"
12
19
  end
13
20
 
14
21
  desc "Synchronize Xmpp Server database with Social Stream Rails Application database"
15
22
  desc "Remove all rosters and populate rosters from Social Stream data."
16
- task :rosters => :environment do
23
+ task :rosters, [:domain] => :environment do |t, args|
17
24
  puts "Starting presence:synchronize:rosters"
18
- SocialStream::Presence::XmppServerOrder::synchronizeRosters
25
+ unless args[:domain]
26
+ puts "No web domain specified"
27
+ domain = SocialStream::Presence.domain
28
+ puts "Executing rake presence:synchronize:rosters[" + domain + "]"
29
+ else
30
+ domain = args[:domain]
31
+ end
32
+ SocialStream::Presence::XmppServerOrder::synchronizeRosters(domain)
19
33
  puts "Rosters Synchronization complete"
20
34
  end
21
35
  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
+ }