opentok 0.1.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -2
  3. data/.travis.yml +6 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +47 -0
  6. data/DEVELOPING.md +91 -0
  7. data/LICENSE +19 -5
  8. data/README.md +170 -53
  9. data/Rakefile +10 -5
  10. data/doc/OpenTok.html +411 -0
  11. data/doc/OpenTok/Archive.html +1320 -0
  12. data/doc/OpenTok/ArchiveList.html +216 -0
  13. data/doc/OpenTok/Archives.html +1028 -0
  14. data/doc/OpenTok/Client.html +695 -0
  15. data/doc/OpenTok/OpenTok.html +1046 -0
  16. data/doc/OpenTok/OpenTokArchiveError.html +142 -0
  17. data/doc/OpenTok/OpenTokAuthenticationError.html +143 -0
  18. data/doc/OpenTok/OpenTokError.html +138 -0
  19. data/doc/OpenTok/Session.html +665 -0
  20. data/doc/OpenTok/TokenGenerator.html +204 -0
  21. data/doc/OpenTok/TokenGenerator/ClassMethods.html +187 -0
  22. data/doc/README.md +15 -0
  23. data/doc/_index.html +182 -0
  24. data/doc/class_list.html +54 -0
  25. data/doc/css/common.css +1 -0
  26. data/doc/css/full_list.css +57 -0
  27. data/doc/css/style.css +339 -0
  28. data/doc/file.README.html +87 -0
  29. data/doc/file_list.html +56 -0
  30. data/doc/frames.html +26 -0
  31. data/doc/index.html +87 -0
  32. data/doc/js/app.js +219 -0
  33. data/doc/js/full_list.js +178 -0
  34. data/doc/js/jquery.js +4 -0
  35. data/doc/method_list.html +227 -0
  36. data/doc/top-level-namespace.html +112 -0
  37. data/lib/opentok.rb +3 -14
  38. data/lib/opentok/archive.rb +92 -0
  39. data/lib/opentok/archive_list.rb +17 -0
  40. data/lib/opentok/archives.rb +120 -0
  41. data/lib/opentok/client.rb +125 -0
  42. data/lib/opentok/constants.rb +5 -0
  43. data/lib/opentok/exceptions.rb +10 -0
  44. data/lib/opentok/opentok.rb +174 -0
  45. data/lib/opentok/session.rb +76 -0
  46. data/lib/opentok/token_generator.rb +101 -0
  47. data/lib/opentok/version.rb +4 -0
  48. data/opentok.gemspec +29 -22
  49. data/sample/Archiving/Gemfile +4 -0
  50. data/sample/Archiving/README.md +212 -0
  51. data/sample/Archiving/archiving_sample.rb +80 -0
  52. data/sample/Archiving/public/css/sample.css +22 -0
  53. data/sample/Archiving/public/img/archiving-off.png +0 -0
  54. data/sample/Archiving/public/img/archiving-on-idle.png +0 -0
  55. data/sample/Archiving/public/img/archiving-on-message.png +0 -0
  56. data/sample/Archiving/public/js/host.js +37 -0
  57. data/sample/Archiving/public/js/participant.js +13 -0
  58. data/sample/Archiving/views/history.erb +65 -0
  59. data/sample/Archiving/views/host.erb +69 -0
  60. data/sample/Archiving/views/index.erb +48 -0
  61. data/sample/Archiving/views/layout.erb +29 -0
  62. data/sample/Archiving/views/participant.erb +55 -0
  63. data/sample/HelloWorld/Gemfile +4 -0
  64. data/sample/HelloWorld/README.md +123 -0
  65. data/sample/HelloWorld/hello_world.rb +27 -0
  66. data/sample/HelloWorld/public/js/helloworld.js +32 -0
  67. data/sample/HelloWorld/views/index.erb +21 -0
  68. data/spec/cassettes/OpenTok_Archives/should_create_archives.yml +48 -0
  69. data/spec/cassettes/OpenTok_Archives/should_create_named_archives.yml +49 -0
  70. data/spec/cassettes/OpenTok_Archives/should_delete_an_archive_by_id.yml +32 -0
  71. data/spec/cassettes/OpenTok_Archives/should_find_archives_by_id.yml +46 -0
  72. data/spec/cassettes/OpenTok_Archives/should_stop_archives.yml +48 -0
  73. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_all_archives.yml +104 -0
  74. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_archives_with_an_offset.yml +71 -0
  75. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_count_number_of_archives.yml +60 -0
  76. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_part_of_the_archives_when_using_offset_and_count.yml +82 -0
  77. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_default_sessions.yml +39 -0
  78. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions.yml +39 -0
  79. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions_with_a_location_hint.yml +39 -0
  80. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions.yml +39 -0
  81. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions_for_invalid_media_modes.yml +39 -0
  82. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions_with_a_location_hint.yml +39 -0
  83. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_sessions_with_a_location_hint.yml +39 -0
  84. data/spec/matchers/token.rb +48 -0
  85. data/spec/opentok/archives_spec.rb +91 -0
  86. data/spec/opentok/opentok_spec.rb +144 -0
  87. data/spec/opentok/session_spec.rb +71 -0
  88. data/spec/shared/opentok_generates_tokens.rb +62 -0
  89. data/spec/shared/session_generates_tokens.rb +63 -0
  90. data/spec/spec_helper.rb +6 -7
  91. metadata +197 -59
  92. data/.rspec +0 -3
  93. data/CHANGES +0 -33
  94. data/doc/reference.md +0 -122
  95. data/lib/open_tok/archive.rb +0 -53
  96. data/lib/open_tok/archive_timeline_event.rb +0 -22
  97. data/lib/open_tok/archive_video_resource.rb +0 -28
  98. data/lib/open_tok/exception.rb +0 -50
  99. data/lib/open_tok/open_tok_sdk.rb +0 -198
  100. data/lib/open_tok/request.rb +0 -63
  101. data/lib/open_tok/role_constants.rb +0 -18
  102. data/lib/open_tok/session.rb +0 -25
  103. data/lib/open_tok/session_property_constants.rb +0 -30
  104. data/lib/open_tok/utils.rb +0 -10
  105. data/lib/open_tok/version.rb +0 -5
  106. data/sample/sample.rb +0 -26
  107. data/spec/cassettes/archives.yml +0 -83
  108. data/spec/cassettes/deleteArchive.yml +0 -91
  109. data/spec/cassettes/invalidSession.yml +0 -41
  110. data/spec/cassettes/session.yml +0 -46
  111. data/spec/cassettes/stitchArchive.yml +0 -42
  112. data/spec/opentok_exception_spec.rb +0 -38
  113. data/spec/opentok_spec.rb +0 -135
@@ -0,0 +1,80 @@
1
+ require 'sinatra/base'
2
+ require 'opentok'
3
+
4
+ raise "You must define API_KEY and API_SECRET environment variables" unless ENV.has_key?("API_KEY") && ENV.has_key?("API_SECRET")
5
+
6
+ class ArchivingSample < Sinatra::Base
7
+
8
+ set :api_key, ENV['API_KEY']
9
+ set :opentok, OpenTok::OpenTok.new(api_key, ENV['API_SECRET'])
10
+ set :session, opentok.create_session
11
+ set :erb, :layout => :layout
12
+
13
+ get '/' do
14
+ erb :index
15
+ end
16
+
17
+ get '/host' do
18
+ api_key = settings.api_key
19
+ session_id = settings.session.session_id
20
+ token = settings.opentok.generate_token(session_id, :role => :moderator)
21
+
22
+ erb :host, :locals => {
23
+ :api_key => api_key,
24
+ :session_id => session_id,
25
+ :token => token
26
+ }
27
+ end
28
+
29
+ get '/participant' do
30
+ api_key = settings.api_key
31
+ session_id = settings.session.session_id
32
+ token = settings.opentok.generate_token(session_id, :role => :moderator)
33
+
34
+ erb :participant, :locals => {
35
+ :api_key => api_key,
36
+ :session_id => session_id,
37
+ :token => token
38
+ }
39
+ end
40
+
41
+ get '/history' do
42
+ page = (params[:page] || "1").to_i
43
+ offset = (page - 1) * 5
44
+ archives = settings.opentok.archives.all(:offset => offset, :count => 5)
45
+
46
+ show_previous = page > 1 ? '/history?page=' + (page-1).to_s : nil
47
+ show_next = archives.total > (offset + 5) ? '/history?page=' + (page+1).to_s : nil
48
+
49
+ erb :history, :locals => {
50
+ :archives => archives,
51
+ :show_previous => show_previous,
52
+ :show_next => show_next
53
+ }
54
+ end
55
+
56
+ get '/download/:archive_id' do
57
+ archive = settings.opentok.archives.find(params[:archive_id])
58
+ redirect archive.url
59
+ end
60
+
61
+ get '/start' do
62
+ archive = settings.opentok.archives.create settings.session.session_id, {
63
+ :name => "Ruby Archiving Sample App"
64
+ }
65
+ body archive.to_json
66
+ end
67
+
68
+ get '/stop/:archive_id' do
69
+ archive = settings.opentok.archives.stop_by_id(params[:archive_id])
70
+ body archive.to_json
71
+ end
72
+
73
+ get '/delete/:archive_id' do
74
+ settings.opentok.archives.delete_by_id(params[:archive_id])
75
+ redirect '/history'
76
+ end
77
+
78
+ # start the server if ruby file executed directly
79
+ run! if app_file == $0
80
+ end
@@ -0,0 +1,22 @@
1
+ /* Move down content because we have a fixed navbar that is 50px tall */
2
+ body {
3
+ padding-top: 50px;
4
+ padding-bottom: 20px;
5
+ background-color: #F2F2F2;
6
+ }
7
+
8
+ /* Responsive: Portrait tablets and up */
9
+ @media screen and (min-width: 768px) {
10
+ /* Remove padding from wrapping element since we kick in the grid classes here */
11
+ .body-content {
12
+ padding: 0;
13
+ }
14
+ }
15
+
16
+ #subscribers div {
17
+ float: left;
18
+ }
19
+
20
+ .bump-me {
21
+ padding-top: 40px;
22
+ }
@@ -0,0 +1,37 @@
1
+ var session = OT.initSession(sessionId),
2
+ publisher = OT.initPublisher("publisher"),
3
+ archiveID = null;
4
+
5
+ session.connect(apiKey, token, function(err, info) {
6
+ if(err) {
7
+ alert(err.message || err);
8
+ }
9
+ session.publish(publisher);
10
+ });
11
+
12
+ session.on('streamCreated', function(event) {
13
+ session.subscribe(event.stream, "subscribers", { insertMode: "append" });
14
+ });
15
+
16
+ session.on('archiveStarted', function(event) {
17
+ archiveID = event.id;
18
+ console.log("ARCHIVE STARTED");
19
+ $(".start").hide();
20
+ $(".stop").show();
21
+ });
22
+
23
+ session.on('archiveStopped', function(event) {
24
+ archiveID = null;
25
+ console.log("ARCHIVE STOPPED");
26
+ $(".start").show();
27
+ $(".stop").hide();
28
+ });
29
+
30
+ $(document).ready(function() {
31
+ $(".start").click(function(event){
32
+ $.get("start");
33
+ }).show();
34
+ $(".stop").click(function(event){
35
+ $.get("stop/" + archiveID);
36
+ }).hide();
37
+ });
@@ -0,0 +1,13 @@
1
+ var session = OT.initSession(sessionId),
2
+ publisher = OT.initPublisher("publisher");
3
+
4
+ session.connect(apiKey, token, function(err, info) {
5
+ if(err) {
6
+ alert(err.message || err);
7
+ }
8
+ session.publish(publisher);
9
+ });
10
+
11
+ session.on('streamCreated', function(event) {
12
+ session.subscribe(event.stream, "subscribers", { insertMode : "append" });
13
+ });
@@ -0,0 +1,65 @@
1
+ <div class="container bump-me">
2
+
3
+ <div class="body-content">
4
+
5
+ <div class="panel panel-default">
6
+ <div class="panel-heading">
7
+ <h3 class="panel-title">Past Recordings</h3>
8
+ </div>
9
+ <div class="panel-body">
10
+ <% if archives.count > 0 %>
11
+ <table class="table">
12
+ <thead>
13
+ <tr>
14
+ <th>&nbsp;</th>
15
+ <th>Created</th>
16
+ <th>Duration</th>
17
+ <th>Status</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% for item in archives %>
22
+
23
+ <tr data-item-id="<%= item.id %>">
24
+ <td>
25
+ <% if (item.status == "available") && item.url && (item.url.length > 0) %>
26
+ <a href="/download/<%= item.id %>">
27
+ <% end %>
28
+ <%= item.name || "Untitled" %>
29
+ <% if (item.status == "available") && item.url && (item.url.length > 0) %>
30
+ </a>
31
+ <% end %>
32
+ </td>
33
+ <td><%= Time.at(item.created_at/1000).strftime("%B %e, %Y at %I:%M %p") %></td>
34
+ <td><%= item.duration %> seconds</td>
35
+ <td><%= item.status %></td>
36
+ <td>
37
+ <% if item.status == "available" %>
38
+ <a href="/delete/<%= item.id %>">Delete</a>
39
+ <% else %>
40
+ &nbsp;
41
+ <% end %>
42
+ </td>
43
+ </tr>
44
+
45
+ <% end %>
46
+ </tbody>
47
+ </table>
48
+ <% else %>
49
+ <p>
50
+ There are no archives currently. Try making one in the <a href="/host">host view</a>.
51
+ </p>
52
+ <% end %>
53
+ </div>
54
+ <div class="panel-footer">
55
+ <% if show_previous %>
56
+ <a href="<%= show_previous %>" class="pull-left">&larr; Newer</a>
57
+ <% end %>
58
+ &nbsp;
59
+ <% if show_next %>
60
+ <a href="<%= show_next %>" class="pull-right">Older &rarr;</a>
61
+ <% end %>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
@@ -0,0 +1,69 @@
1
+ <script src="//static.opentok.com/webrtc/v2.2/js/opentok.min.js"></script>
2
+
3
+ <div class="container bump-me">
4
+
5
+ <div class="body-content">
6
+
7
+ <div class="panel panel-default">
8
+ <div class="panel-heading">
9
+ <h3 class="panel-title">Host</h3>
10
+ </div>
11
+ <div class="panel-body">
12
+ <div id="subscribers"><div id="publisher"></div></div>
13
+ </div>
14
+ <div class="panel-footer">
15
+ <button class="btn btn-danger start">Start archiving</button>
16
+ <button class="btn btn-success stop">Stop archiving</button>
17
+ </div>
18
+ </div>
19
+ </div>
20
+
21
+ <div class="panel panel-default">
22
+ <div class="panel-heading">
23
+ <h3 class="panel-title">Instructions</h3>
24
+ </div>
25
+ <div class="panel-body">
26
+ <p>
27
+ Click <strong>Start archiving</strong> to begin archiving this session.
28
+ All publishers in the session will be included, and all publishers that
29
+ join the session will be included as well.
30
+ </p>
31
+ <p>
32
+ Click <strong>Stop archiving</strong> to end archiving this session.
33
+ You can then go to <a href="/history">past archives</a> to
34
+ view your archive (once its status changes to available).
35
+ </p>
36
+ <table class="table">
37
+ <thead>
38
+ <tr>
39
+ <th>When</th>
40
+ <th>You will see</th>
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr>
45
+ <td style="vertical-align: middle;">Archiving is started</td>
46
+ <td><img src="img/archiving-on-message.png"></td>
47
+ </tr>
48
+ <tr>
49
+ <td style="vertical-align: middle;">Archiving remains on</td>
50
+ <td><img src="img/archiving-on-idle.png"></td>
51
+ </tr>
52
+ <tr>
53
+ <td style="vertical-align: middle;">Archiving is stopped</td>
54
+ <td><img src="img/archiving-off.png"></td>
55
+ </tr>
56
+ </tbody>
57
+ </table>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <script>
63
+ var sessionId = "<%= session_id %>";
64
+ var apiKey = "<%= api_key %>";
65
+ var token = "<%= token %>";
66
+ </script>
67
+ <script src="/js/host.js"></script>
68
+
69
+ </div>
@@ -0,0 +1,48 @@
1
+ <div class="container bump-me">
2
+
3
+
4
+ <div class="body-content">
5
+
6
+
7
+ <div class="row">
8
+ <div class="col-lg-6 col-offset-1">
9
+
10
+ <div class="panel panel-default">
11
+ <div class="panel-heading">Create an archive</div>
12
+ <div class="panel-body">
13
+ <p>
14
+ Everyone who joins either the Host View or Participant View
15
+ joins a single OpenTok session. Anyone with the Host View
16
+ open can click Start Archive or Stop Archive to control
17
+ recording of the entire session.
18
+ </p>
19
+ </div>
20
+ <div class="panel-footer">
21
+ <a class="btn btn-danger" href="host">Host View</a>
22
+ <a class="btn btn-danger" href="participant">Participant View</a>
23
+ </div>
24
+ </div>
25
+
26
+ </div>
27
+ <div class="col-lg-6 col-offset-1">
28
+
29
+ <div class="panel panel-default">
30
+ <div class="panel-heading">Play an archive</div>
31
+ <div class="panel-body">
32
+ <p>
33
+ Click through to Past Archives to see examples of using the
34
+ Archiving REST API to list archives showing status (started,
35
+ stopped, available) and playback (for available archives).
36
+ </p>
37
+ </div>
38
+ <div class="panel-footer">
39
+ <a class="btn btn-success" href="history">Past Archives</a>
40
+ </div>
41
+ </div>
42
+
43
+ </div>
44
+ </div>
45
+
46
+ </div>
47
+
48
+ </div>
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <title>Archiving Sample</title>
7
+ <meta name="description" content="">
8
+ <meta name="viewport" content="width=device-width">
9
+
10
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap.min.css">
11
+ <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap-theme.min.css">
12
+ <link rel="stylesheet" href="css/sample.css">
13
+
14
+ <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
15
+ </head>
16
+ <body>
17
+
18
+ <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
19
+ <!-- Brand and toggle get grouped for better mobile display -->
20
+ <div class="navbar-header">
21
+ <a class="navbar-brand" href="./">Archiving Sample App</a>
22
+ </div>
23
+ </nav>
24
+
25
+ <%= yield %>
26
+
27
+ <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.js"></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,55 @@
1
+ <script src="//static.opentok.com/webrtc/v2.2/js/opentok.min.js"></script>
2
+
3
+ <div class="container bump-me">
4
+
5
+ <div class="body-content">
6
+
7
+ <div class="panel panel-default">
8
+ <div class="panel-heading">
9
+ <h3 class="panel-title">Participant</h3>
10
+ </div>
11
+ <div class="panel-body">
12
+ <div id="subscribers"><div id="publisher"></div></div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="panel panel-default">
18
+ <div class="panel-heading">
19
+ <h3 class="panel-title">Instructions</h3>
20
+ </div>
21
+ <div class="panel-body">
22
+ <table class="table">
23
+ <thead>
24
+ <tr>
25
+ <th>When</th>
26
+ <th>You will see</th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr>
31
+ <td style="vertical-align: middle;">Archiving is started</td>
32
+ <td><img src="img/archiving-on-message.png"></td>
33
+ </tr>
34
+ <tr>
35
+ <td style="vertical-align: middle;">Archiving remains on</td>
36
+ <td><img src="img/archiving-on-idle.png"></td>
37
+ </tr>
38
+ <tr>
39
+ <td style="vertical-align: middle;">Archiving is stopped</td>
40
+ <td><img src="img/archiving-off.png"></td>
41
+ </tr>
42
+ </tbody>
43
+ </table>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <script>
49
+ var sessionId = "<%= session_id %>";
50
+ var apiKey = "<%= api_key %>";
51
+ var token = "<%= token %>";
52
+ </script>
53
+ <script src="/js/participant.js"></script>
54
+
55
+ </div>
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "sinatra", "~> 1.4.4"
4
+ gem "opentok", :path => "../../"
@@ -0,0 +1,123 @@
1
+ # OpenTok Hello World Ruby
2
+
3
+ This is a simple demo app that shows how you can use the OpenTok-Ruby-SDK to create Sessions,
4
+ generate Tokens with those Sessions, and then pass these values to a JavaScript client that can
5
+ connect and conduct a group chat.
6
+
7
+ ## Running the App
8
+
9
+ First, download the dependencies using [Bundler](http://bundler.io)
10
+
11
+ ```
12
+ $ bundle install
13
+ ```
14
+
15
+ Next, add your own API Key and API Secret to the environment variables. There are a few ways to do
16
+ this but the simplest would be to do it right in your shell.
17
+
18
+ ```
19
+ $ export API_KEY=0000000
20
+ $ export API_SECRET=abcdef1234567890abcdef01234567890abcdef
21
+ ```
22
+
23
+ Finally, start the server using Bundler to handle dependencies
24
+
25
+ ```
26
+ $ bundle exec ruby hello_world.rb
27
+ ```
28
+
29
+ Visit <http://localhost:9393> in your browser. Open it again in a second window. Smile! You've just
30
+ set up a group chat.
31
+
32
+ ## Walkthrough
33
+
34
+ This demo application uses the [Sinatra web framework](http://www.sinatrarb.com/). It is similar to
35
+ many other popular web frameworks, such as [Rails](http://rubyonrails.org/), but more light-weight.
36
+ We are only covering the very basics of the framework, but you can learn more by following the links
37
+ above.
38
+
39
+ ### Main Application (hello_world.rb)
40
+
41
+ The first thing done in this file is to require the dependencies we will be using. In this case that
42
+ is the Sinatra web framework and most importantly the OpenTok SDK. By running the application with
43
+ the `bundle exec` command, we allow Bundler to select the right versions of these gems from the
44
+ right place.
45
+
46
+ ```ruby
47
+ require 'sinatra/base'
48
+ require 'opentok'
49
+ ```
50
+
51
+ Next this file performs some basic checks on the environment. If it cannot find the `API_KEY`and
52
+ `API_SECRET` environment variables, there is no point in continuing.
53
+
54
+ The class `HelloWorld` is our application. The first thing it does is to initialize an instance of
55
+ OpenTok and also store it using Sinatra's `set` method so it can be accessed in other parts of the
56
+ application. At the same time, we also `set` the `api_key` separately so that we can access
57
+ it on its own.
58
+
59
+ ```ruby
60
+ set :api_key, ENV['API_KEY']
61
+ set :opentok, OpenTok::OpenTok.new(api_key, ENV['API_SECRET'])
62
+ ```
63
+
64
+ Now, lets discuss the Hello World application's functionality. We want to set up a group chat so
65
+ that any client that visits a page will connect to the same OpenTok Session. Once they are connected
66
+ they can Publish a Stream and Subscribe to all the other streams in that Session. So we just need
67
+ one Session object, and it needs to be accessible every time a request is made. The next line of our
68
+ application simply calls the `OpenTok` instance's `create_session` method and once again uses `set`
69
+ to store it. Alternatively, `session_id`s are commonly stored in databses for applications that have
70
+ many of them.
71
+
72
+ ```ruby
73
+ set :session, opentok.create_session
74
+ ```
75
+
76
+ We only need one page, so we create one route handler for any HTTP GET requests to trigger.
77
+
78
+ ```ruby
79
+ get '\' do
80
+ # ...
81
+ end
82
+ ```
83
+
84
+ Now all we have to do is serve a page with the three values the client will need to connect to the
85
+ session: `api_key`, `session_id`, and `token`. The first two are right on the `settings` object
86
+ because we called `set` earlier. Note that the we call the `session_id` method to get just that
87
+ value from the `session` object. The `token` is generated freshly on this request by calling the
88
+ `generate_token` method of the `opentok` instance, and passing in the `session_id`. This is because
89
+ a Token is a piece of information that carries a specific client's permissions in a certain Session.
90
+ Ideally, as we've done here, you generate a unique token for each client that will connect.
91
+
92
+ ```ruby
93
+ api_key = settings.api_key
94
+ session_id = settings.session.session_id
95
+ token = settings.opentok.generate_token(session_id)
96
+ ```
97
+
98
+ Now all we have to do is serve a page with those three values. Lets call our `erb` helper that will
99
+ pick up a template called `index.erb` from the `views/` directory in our application and pass in
100
+ the local variables for it to print using the `:locals` hash.
101
+
102
+ ```ruby
103
+ erb :index, :locals => {
104
+ :api_key => api_key,
105
+ :session_id => session_id,
106
+ :token => token
107
+ }
108
+ ```
109
+
110
+ ### Main Template (views/index.rb)
111
+
112
+ This file simply sets up the HTML page for the JavaScript application to run, imports the
113
+ JavaScript library, and passes the values created by the server into the JavaScript application
114
+ inside `public/js/helloworld.js`
115
+
116
+ ### JavaScript Applicaton (public/js/helloworld.js)
117
+
118
+ The group chat is mostly implemented in this file. At a high level, we connect to the given
119
+ Session, publish a stream from our webcam, and listen for new streams from other clients to
120
+ subscribe to.
121
+
122
+ For more details, read the comments in the file or go to the
123
+ [JavaScript Client Library](http://tokbox.com/opentok/libraries/client/js/) for a full reference.