opentok 3.1.0 → 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/metrics.yml +17 -0
  3. data/.travis.yml +3 -1
  4. data/CODE_OF_CONDUCT.md +128 -0
  5. data/README.md +98 -49
  6. data/lib/opentok/archives.rb +13 -2
  7. data/lib/opentok/broadcasts.rb +1 -1
  8. data/lib/opentok/client.rb +6 -4
  9. data/lib/opentok/constants.rb +1 -1
  10. data/lib/opentok/opentok.rb +4 -2
  11. data/lib/opentok/sip.rb +0 -2
  12. data/lib/opentok/version.rb +1 -1
  13. data/opentok.gemspec +9 -8
  14. data/sample/Broadcast/Gemfile +4 -0
  15. data/sample/Broadcast/README.md +201 -0
  16. data/sample/Broadcast/broadcast_sample.rb +97 -0
  17. data/sample/Broadcast/public/css/sample.css +64 -0
  18. data/sample/Broadcast/public/js/host.js +185 -0
  19. data/sample/Broadcast/public/js/participant.js +85 -0
  20. data/sample/Broadcast/views/host.erb +82 -0
  21. data/sample/Broadcast/views/index.erb +32 -0
  22. data/sample/Broadcast/views/layout.erb +29 -0
  23. data/sample/Broadcast/views/participant.erb +27 -0
  24. data/sample/HelloWorld/public/js/helloworld.js +4 -10
  25. data/sample/HelloWorld/views/index.erb +1 -3
  26. data/spec/cassettes/OpenTok_Archives/calls_layout_on_archive_object.yml +3 -1
  27. data/spec/cassettes/OpenTok_Archives/changes_the_layout_of_an_archive.yml +3 -1
  28. data/spec/cassettes/OpenTok_Archives/http_client_errors/.yml +34 -0
  29. data/spec/cassettes/OpenTok_Archives/should_create_archives.yml +3 -1
  30. data/spec/cassettes/OpenTok_Archives/should_create_audio_only_archives.yml +3 -1
  31. data/spec/cassettes/OpenTok_Archives/should_create_custom_layout_archives.yml +50 -0
  32. data/spec/cassettes/OpenTok_Archives/should_create_hd_archives.yml +3 -1
  33. data/spec/cassettes/OpenTok_Archives/should_create_individual_archives.yml +3 -1
  34. data/spec/cassettes/OpenTok_Archives/should_create_named_archives.yml +3 -1
  35. data/spec/cassettes/OpenTok_Archives/should_delete_an_archive_by_id.yml +3 -1
  36. data/spec/cassettes/OpenTok_Archives/should_find_archives_by_id.yml +3 -1
  37. data/spec/cassettes/OpenTok_Archives/should_find_archives_with_unknown_properties.yml +3 -1
  38. data/spec/cassettes/OpenTok_Archives/should_find_expired_archives.yml +3 -1
  39. data/spec/cassettes/OpenTok_Archives/should_find_paused_archives_by_id.yml +3 -1
  40. data/spec/cassettes/OpenTok_Archives/should_stop_archives.yml +3 -1
  41. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_all_archives.yml +3 -1
  42. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_archives_with_an_offset.yml +3 -1
  43. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_count_number_of_archives.yml +3 -1
  44. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_part_of_the_archives_when_using_offset_and_count.yml +3 -1
  45. data/spec/cassettes/OpenTok_Archives/when_many_archives_are_created/should_return_session_archives.yml +3 -1
  46. data/spec/cassettes/OpenTok_Broadcasts/calls_layout_on_broadcast_object.yml +3 -1
  47. data/spec/cassettes/OpenTok_Broadcasts/changes_the_layout_of_a_broadcast.yml +3 -1
  48. data/spec/cassettes/OpenTok_Broadcasts/fetches_a_hls_broadcast_url.yml +3 -1
  49. data/spec/cassettes/OpenTok_Broadcasts/finds_a_broadcast.yml +3 -1
  50. data/spec/cassettes/OpenTok_Broadcasts/starts_a_rtmp_broadcast.yml +3 -1
  51. data/spec/cassettes/OpenTok_Broadcasts/stops_a_broadcast.yml +3 -1
  52. data/spec/cassettes/OpenTok_Connections/forces_a_connection_to_be_terminated.yml +3 -1
  53. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_always_archived_sessions.yml +3 -1
  54. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_default_sessions.yml +3 -1
  55. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions.yml +3 -1
  56. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions_for_invalid_media_modes.yml +3 -1
  57. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_relayed_media_sessions_with_a_location_hint.yml +3 -1
  58. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions.yml +3 -1
  59. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_routed_media_sessions_with_a_location_hint.yml +3 -1
  60. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/_create_session/creates_sessions_with_a_location_hint.yml +3 -1
  61. data/spec/cassettes/OpenTok_OpenTok/when_initialized_properly/with_an_addendum_to_the_user_agent_string/should_append_the_addendum_to_the_user_agent_header.yml +3 -1
  62. data/spec/cassettes/OpenTok_Signals/receives_a_valid_response_for_a_connection.yml +3 -1
  63. data/spec/cassettes/OpenTok_Signals/receives_a_valid_response_for_all_connections.yml +3 -1
  64. data/spec/cassettes/OpenTok_Sip/receives_a_valid_response.yml +3 -1
  65. data/spec/cassettes/OpenTok_Streams/get_all_streams_information.yml +3 -1
  66. data/spec/cassettes/OpenTok_Streams/get_specific_stream_information.yml +3 -1
  67. data/spec/cassettes/OpenTok_Streams/layout_working_on_two_stream_list.yml +3 -1
  68. data/spec/opentok/archives_spec.rb +11 -1
  69. data/spec/opentok/broadcasts_spec.rb +25 -1
  70. data/spec/opentok/client_spec.rb +51 -0
  71. data/spec/opentok/opentok_spec.rb +21 -0
  72. data/spec/spec_helper.rb +2 -0
  73. metadata +39 -22
@@ -48,6 +48,17 @@ module OpenTok
48
48
  # default) or "1280x720" (HD). This property only applies to composed archives. If you set
49
49
  # this property and set the outputMode property to "individual", the call the method
50
50
  # results in an error.
51
+ # @option options [Hash] :layout Specify this to assign the initial layout type for
52
+ # the archive. This applies only to composed archives. This is a hash containing two keys:
53
+ # <code>:type</code> and <code>:stylesheet<code>. Valid values for <code>:type</code> are
54
+ # "bestFit" (best fit), "custom" (custom), "horizontalPresentation" (horizontal presentation),
55
+ # "pip" (picture-in-picture), and "verticalPresentation" (vertical presentation)).
56
+ # If you specify a "custom" layout type, set the <code>:stylesheet</code> key to the
57
+ # stylesheet (CSS). (For other layout types, do not set the <code>:stylesheet</code> key.)
58
+ # If you do not specify an initial layout type, the archive uses the best fit
59
+ # layout type. For more information, see
60
+ # {https://tokbox.com/developer/guides/archiving/layout-control.html Customizing
61
+ # the video layout for composed archives}.
51
62
  #
52
63
  # @return [Archive] The Archive object, which includes properties defining the archive,
53
64
  # including the archive ID.
@@ -66,7 +77,7 @@ module OpenTok
66
77
  "Resolution cannot be supplied for individual output mode" if options.key?(:resolution) and options[:output_mode] == :individual
67
78
 
68
79
  # normalize opts so all keys are symbols and only include valid_opts
69
- valid_opts = [ :name, :has_audio, :has_video, :output_mode, :resolution ]
80
+ valid_opts = [ :name, :has_audio, :has_video, :output_mode, :resolution, :layout ]
70
81
  opts = options.inject({}) do |m,(k,v)|
71
82
  if valid_opts.include? k.to_sym
72
83
  m[k.to_sym] = v
@@ -198,7 +209,7 @@ module OpenTok
198
209
  type = options[:type]
199
210
  raise ArgumentError, "custom type must have a stylesheet" if (type.eql? "custom") && (!options.key? :stylesheet)
200
211
  valid_non_custom_type = ["bestFit","horizontalPresentation","pip", "verticalPresentation", ""].include? type
201
- raise ArgumentError, "type is not valid or stylesheet not needed" if !valid_non_custom_type
212
+ raise ArgumentError, "type is not valid" if !valid_non_custom_type && !(type.eql? "custom")
202
213
  raise ArgumentError, "type is not valid or stylesheet not needed" if valid_non_custom_type and options.key? :stylesheet
203
214
  response = @client.layout_archive(archive_id, options)
204
215
  (200..300).include? response.code
@@ -138,7 +138,7 @@ module OpenTok
138
138
  type = options[:type]
139
139
  raise ArgumentError, "custom type must have a stylesheet" if (type.eql? "custom") && (!options.key? :stylesheet)
140
140
  valid_non_custom_type = ["bestFit","horizontalPresentation","pip", "verticalPresentation", ""].include? type
141
- raise ArgumentError, "type is not valid" if !valid_non_custom_type
141
+ raise ArgumentError, "type is not valid" if !valid_non_custom_type && !(type.eql? "custom")
142
142
  raise ArgumentError, "stylesheet not needed" if valid_non_custom_type and options.key? :stylesheet
143
143
  response = @client.layout_broadcast(broadcast_id, options)
144
144
  (200..300).include? response.code
@@ -12,18 +12,20 @@ module OpenTok
12
12
  class Client
13
13
  include HTTParty
14
14
 
15
- open_timeout 2 # Set HTTParty default timeout (open/read) to 2 seconds
16
-
17
15
  # TODO: expose a setting for http debugging for developers
18
16
  # debug_output $stdout
19
17
 
20
- def initialize(api_key, api_secret, api_url, ua_addendum="")
18
+ attr_accessor :api_key, :api_secret, :api_url, :ua_addendum, :timeout_length
19
+
20
+ def initialize(api_key, api_secret, api_url, ua_addendum='', opts={})
21
21
  self.class.base_uri api_url
22
22
  self.class.headers({
23
- "User-Agent" => "OpenTok-Ruby-SDK/#{VERSION}" + "-Ruby-Version-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" + (ua_addendum ? " #{ua_addendum}" : "")
23
+ "User-Agent" => "OpenTok-Ruby-SDK/#{VERSION}" + "-Ruby-Version-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" + (ua_addendum ? " #{ua_addendum}" : "")
24
24
  })
25
25
  @api_key = api_key
26
26
  @api_secret = api_secret
27
+ @timeout_length = opts[:timeout_length] || 2
28
+ self.class.open_timeout @timeout_length
27
29
  end
28
30
 
29
31
  def generate_jwt(api_key, api_secret)
@@ -2,6 +2,6 @@ module OpenTok
2
2
  API_URL = "https://api.opentok.com"
3
3
  TOKEN_SENTINEL = "T1=="
4
4
  ROLES = { subscriber: "subscriber", publisher: "publisher", moderator: "moderator" }
5
- ARCHIVE_MODES = Set.new([:manual, :always])
5
+ ARCHIVE_MODES = ::Set.new([:manual, :always])
6
6
  AUTH_EXPIRE = 300
7
7
  end
@@ -69,7 +69,7 @@ module OpenTok
69
69
  # @private
70
70
  # don't want these to be mutable, may cause bugs related to inconsistency since these values are
71
71
  # cached in objects that this can create
72
- attr_reader :api_key, :api_secret, :api_url, :ua_addendum
72
+ attr_reader :api_key, :api_secret, :timeout_length, :api_url, :ua_addendum
73
73
 
74
74
  ##
75
75
  # Create a new OpenTok object.
@@ -79,9 +79,11 @@ module OpenTok
79
79
  # @param [String] api_secret Your OpenTok API key.
80
80
  # @option opts [Symbol] :api_url Do not set this parameter. It is for internal use by TokBox.
81
81
  # @option opts [Symbol] :ua_addendum Do not set this parameter. It is for internal use by TokBox.
82
+ # @option opts [Symbol] :timeout_length Custom timeout in seconds. If not provided, defaults to 2 seconds.
82
83
  def initialize(api_key, api_secret, opts={})
83
84
  @api_key = api_key.to_s()
84
85
  @api_secret = api_secret
86
+ @timeout_length = opts[:timeout_length] || 2
85
87
  @api_url = opts[:api_url] || API_URL
86
88
  @ua_addendum = opts[:ua_addendum]
87
89
  end
@@ -212,7 +214,7 @@ module OpenTok
212
214
 
213
215
  protected
214
216
  def client
215
- @client ||= Client.new api_key, api_secret, api_url, ua_addendum
217
+ @client ||= Client.new api_key, api_secret, api_url, ua_addendum, timeout_length: @timeout_length
216
218
  end
217
219
 
218
220
  end
@@ -30,8 +30,6 @@ module OpenTok
30
30
  # PSTN phones.
31
31
  # @option opts [Hash] :headers This hash defines custom headers to be added
32
32
  # to the SIP ​INVITE​ request initiated from OpenTok to the your SIP platform.
33
- # Each of the custom headers must start with the ​"X-"​ prefix, or the call
34
- # will result in a Bad Request (400) response.
35
33
  # @option opts [Hash] :auth This object contains the username and password
36
34
  # to be used in the the SIP INVITE​ request for HTTP digest authentication,
37
35
  # if it is required by your SIP platform.
@@ -1,4 +1,4 @@
1
1
  module OpenTok
2
2
  # @private
3
- VERSION = '3.1.0'
3
+ VERSION = '4.1.2'
4
4
  end
@@ -16,17 +16,18 @@ Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
 
19
- spec.add_development_dependency "bundler", "~> 1.5"
20
- spec.add_development_dependency "rake", "~> 10.1.1"
21
- spec.add_development_dependency "rspec", "~> 2.14.1"
22
- spec.add_development_dependency "webmock", "~> 2.3.2"
23
- spec.add_development_dependency "vcr", "~> 2.8.0"
24
- spec.add_development_dependency "yard", "~> 0.9.11"
19
+ bundler_version = RUBY_VERSION < '2.1' ? '~> 1.5' : '>= 1.5'
20
+ spec.add_development_dependency "bundler", bundler_version
21
+ spec.add_development_dependency "rake", "~> 12.0.0"
22
+ spec.add_development_dependency "rspec", "~> 3.9.0"
23
+ spec.add_development_dependency "webmock", ">= 2.3.2"
24
+ spec.add_development_dependency "vcr", ">= 2.8.0"
25
+ spec.add_development_dependency "yard", ">= 0.9.11"
25
26
  # TODO: exclude this for compatibility with rbx
26
27
  # spec.add_development_dependency "debugger", "~> 1.6.6"
27
28
 
28
29
  spec.add_dependency "addressable", "~> 2.3" # 2.3.0 <= version < 3.0.0
29
- spec.add_dependency "httparty", "~> 0.15.5"
30
+ spec.add_dependency "httparty", ">= 0.18.0"
30
31
  spec.add_dependency "activesupport", ">= 2.0"
31
- spec.add_dependency "jwt", "~> 1.5.6"
32
+ spec.add_dependency "jwt", ">= 1.5.6"
32
33
  end
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "sinatra", "~> 1.4.4"
4
+ gem "opentok", :path => "../../"
@@ -0,0 +1,201 @@
1
+ # OpenTok Broadcasting Sample for Ruby
2
+
3
+ This is a simple demo app that shows how you can use the OpenTok Ruby SDK to broadcast
4
+ sessions and how to stop them, change the layout of the broadcast and/or the streams within.
5
+
6
+ ## Running the App
7
+
8
+ First, download the dependencies using [Bundler](http://bundler.io)
9
+
10
+ ```
11
+ $ bundle install
12
+ ```
13
+
14
+ Next, add your OpenTok API key and API secret to the environment variables. There are a few ways to do
15
+ this but the simplest would be to do it right in your shell.
16
+
17
+ ```
18
+ $ export API_KEY=0000000
19
+ $ export API_SECRET=abcdef1234567890abcdef01234567890abcdef
20
+ ```
21
+
22
+ Finally, start the server using Bundler to handle dependencies
23
+
24
+ ```
25
+ $ bundle exec ruby broadcast_sample.rb
26
+ ```
27
+
28
+ Visit <http://localhost:4567> in your browser. You can now create new broadcast (with a host and
29
+ as a participant) and also view those broadcasts.
30
+
31
+ ## Walkthrough
32
+
33
+ This demo application uses the same frameworks and libraries as the HelloWorld sample. If you have
34
+ not already gotten familiar with the code in that project, consider doing so before continuing.
35
+
36
+ The explanations below are separated by page. Each section will focus on a route handler within the
37
+ main application (broadcast_sample.rb).
38
+
39
+ ### Creating Broadcasts – Host View
40
+
41
+ The Host view manages the broadcasting process. Visit the host page at <http://localhost:4567/host>.
42
+ Your browser will first ask you to approve permission to use the camera and microphone.
43
+ Once you've accepted, your image will appear inside the section titled 'Host'. To start broadcasting
44
+ the video stream, press the 'Start Broadcast' button. You can specify the maximum duration,
45
+ resolution, and layout of the broadcast. Once broadcasting has begun the button will turn
46
+ green and change to 'Stop Broadcast'. Click this button when you are done broadcasting.
47
+
48
+ The host page basically sets up the OpenTok session with the API key and secret you provided.
49
+ If a previously started broadcast exists, it defaults to it, along with the layout and the stream
50
+ that has the focus:
51
+
52
+ ```ruby
53
+ get '/host' do
54
+ api_key = settings.api_key
55
+ session_id = settings.session.session_id
56
+ token = settings.opentok.generate_token(session_id, role: :publisher, initialLayoutClassList: ['focus'])
57
+
58
+ erb :host, locals: {
59
+ apiKey: api_key,
60
+ sessionId: session_id,
61
+ token: token,
62
+ initialBroadcastId: settings.broadcast_id,
63
+ focusStreamId: settings.focus_stream_id,
64
+ initialLayout: settings.broadcast_layout
65
+ }
66
+ end
67
+ ```
68
+
69
+ This handler generates the three strings that the client (JavaScript) needs to connect
70
+ to the session: `apiKey`, `sessionId`, and `token`. The `initialBroadcastId` is the broadcast ID,
71
+ `focusStreamId` is the stream ID that has the current focus (if there is one), and
72
+ `initialLayout` is the initial layout for the current broadcast in progress (if there is one).
73
+ (We will discuss focus stream and broadcast layout below.)
74
+
75
+ In the host page, the user presses the 'Start Broadcast' button, which sends an XHR (or Ajax)
76
+ request to the <http://localhost:4567/start> URL. The route handler for this URL is shown below:
77
+
78
+ ```ruby
79
+ post '/start' do
80
+ opts = {
81
+ :maxDuration => params.key?("maxDuration") ? params[:maxDuration] : 7200,
82
+ :resolution => params[:resolution],
83
+ :layout => params[:layout],
84
+ :outputs => {
85
+ :hls => {}
86
+ }
87
+ }
88
+ broadcast = settings.opentok.broadcasts.create(settings.session.session_id, opts)
89
+ settings.broadcast_id = broadcast.id
90
+ body broadcast.to_json
91
+ end
92
+ ```
93
+
94
+ In this handler, `opentok.broadcasts.create` is called with the `session_id` for
95
+ the OpenTok session to broadcast. The optional second argument is a hash which defines
96
+ optional properties for the broadcast. It consists of `maxDuration` of the broadcast,
97
+ `resolution`, and broadcast `layout`. This sample app starts an HLS broadcast (not RTMP),
98
+ so it only specifies an `hls` property of the `outputs` property. See the
99
+ [Ruby SDK documentation](https://github.com/opentok/OpenTok-Ruby-SDK) for information
100
+ on adding RTMP broadcast streams. In this case, as in the HelloWorld sample app, there is
101
+ only one session created and it is used here and for the participant view.
102
+ This will trigger the broadcasting to begin. The response sent back to the client’s XHR request
103
+ will be the JSON representation of the broadcast, which is returned from the `to_json()` method.
104
+
105
+ You can view the HLS broadcast by opening the root URL (<http://localhost:4567/>) in
106
+ a different tab and clicking the `Broadcast URL` button. The code for handling this is as follows:
107
+
108
+ ```ruby
109
+ get '/broadcast' do
110
+ return 'No broadcast id exists' if settings.broadcast_id.nil? || settings.broadcast_id.empty?
111
+ broadcast = settings.opentok.broadcasts.find settings.broadcast_id
112
+ redirect broadcast.broadcastUrls['hls'] if broadcast.status == 'started'
113
+ end
114
+ ```
115
+
116
+ The route for Stop Broadcast has the following code:
117
+
118
+ ```ruby
119
+ get '/stop/:broadcastId' do
120
+ broadcast = settings.opentok.broadcasts.stop settings.broadcast_id
121
+ settings.broadcast_id = nil
122
+ settings.focus_stream_id = ''
123
+ settings.broadcast_layout = 'horizontalPresentation'
124
+ body broadcast.to_json
125
+ end
126
+ ```
127
+
128
+ The settings revert backs to the settings when you start the app.
129
+
130
+ The host page includes a `Toggle Layout` button, which toggles between
131
+ `verticalPresentation` and `horizontalPresentation`.
132
+
133
+ The route for `Toggle Layout` has the following code:
134
+
135
+ ```ruby
136
+ post '/broadcast/:broadcastId/layout' do
137
+ layoutType = params[:type]
138
+ settings.opentok.broadcasts.layout(settings.broadcast_id, type: layoutType)
139
+ settings.broadcast_layout = layoutType
140
+ end
141
+ ```
142
+
143
+ This calls the `opentok.broadcasts.layout()` method, setting the broadcast layout to
144
+ the layout type defined in the POST request's body. In this app, the layout type is
145
+ set to `horizontalPresentation` or `verticalPresentation`, two of the [predefined layout
146
+ types](https://tokbox.com/developer/guides/broadcast/live-streaming/#predefined-layout-types)
147
+ available to live streaming broadcasts.
148
+
149
+ ### Creating Broadcast - Participant View
150
+
151
+ With the host view still open and publishing, open an additional tab and navigate to
152
+ <http://localhost:4567/participant> and allow the browser to use your camera and microphone.
153
+ You will now see the participant in the broadcast.
154
+
155
+ ```ruby
156
+ get '/participant' do
157
+ api_key = settings.api_key
158
+ session_id = settings.session.session_id
159
+ token = settings.opentok.generate_token(session_id, role: :publisher)
160
+
161
+ erb :participant, locals: {
162
+ apiKey: api_key,
163
+ sessionId: session_id,
164
+ token: token,
165
+ focusStreamId: settings.focus_stream_id,
166
+ layout: settings.broadcast_layout
167
+ }
168
+ end
169
+ ```
170
+
171
+ ### Changing the layout classes for streams
172
+
173
+ In the host page, if you click on either the host or a participant video, that video gets
174
+ the `focus` layout in the broadcast. The host page sends the `focus` stream ID and
175
+ the other streams' layout class lists can be cleared, as shown below:
176
+
177
+ ```ruby
178
+ post '/focus' do
179
+ hash = { items: [] }
180
+ hash[:items] << { id: params[:focus], layoutClassList: ['focus'] }
181
+ settings.focus_stream_id = params[:focus]
182
+ if params.key?('otherStreams')
183
+ params[:otherStreams].each do |stream|
184
+ hash[:items] << { id: stream, layoutClassList: [] }
185
+ end
186
+ end
187
+ settings.opentok.streams.layout(settings.session.session_id, hash)
188
+ end
189
+ ```
190
+
191
+ The host client page also uses OpenTok signaling to notify other clients when the layout type and
192
+ focus stream changes, and they then update the local display of streams in the HTML DOM accordingly.
193
+ However, this is not necessary. The layout of the broadcast is unrelated to the layout of
194
+ streams in the web clients.
195
+
196
+ When you view the broadcast stream, the layout type and focus stream changes, based on calls
197
+ to the `OpenTok.setBroadcastLayout()` and `OpenTok.setStreamClassLists()` methods during
198
+ the broadcast.
199
+
200
+ For more information, see [Configuring video layout for OpenTok live streaming
201
+ broadcasts](https://tokbox.com/developer/guides/broadcast/live-streaming/#configuring-video-layout-for-opentok-live-streaming-broadcasts).
@@ -0,0 +1,97 @@
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 BroadcastSample < 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(:media_mode => :routed)
11
+ set :erb, :layout => :layout
12
+ set :broadcast_id, nil
13
+ set :focus_stream_id, ''
14
+ set :broadcast_layout, 'horizontalPresentation'
15
+
16
+ get '/' do
17
+ erb :index
18
+ end
19
+
20
+ get '/host' do
21
+ api_key = settings.api_key
22
+ session_id = settings.session.session_id
23
+ token = settings.opentok.generate_token(session_id, role: :publisher, initialLayoutClassList: ['focus'])
24
+
25
+ erb :host, locals: {
26
+ apiKey: api_key,
27
+ sessionId: session_id,
28
+ token: token,
29
+ initialBroadcastId: settings.broadcast_id,
30
+ focusStreamId: settings.focus_stream_id,
31
+ initialLayout: settings.broadcast_layout
32
+ }
33
+ end
34
+
35
+ get '/participant' do
36
+ api_key = settings.api_key
37
+ session_id = settings.session.session_id
38
+ token = settings.opentok.generate_token(session_id, role: :publisher)
39
+
40
+ erb :participant, locals: {
41
+ apiKey: api_key,
42
+ sessionId: session_id,
43
+ token: token,
44
+ focusStreamId: settings.focus_stream_id,
45
+ layout: settings.broadcast_layout
46
+ }
47
+ end
48
+
49
+ post '/start' do
50
+ opts = {
51
+ :maxDuration => params.key?("maxDuration") ? params[:maxDuration] : 7200,
52
+ :resolution => params[:resolution],
53
+ :layout => params[:layout],
54
+ :outputs => {
55
+ :hls => {}
56
+ }
57
+ }
58
+ broadcast = settings.opentok.broadcasts.create(settings.session.session_id, opts)
59
+ settings.broadcast_id = broadcast.id
60
+ body broadcast.to_json
61
+ end
62
+
63
+ get '/broadcast' do
64
+ return 'No broadcast id exists' if settings.broadcast_id.nil? || settings.broadcast_id.empty?
65
+ broadcast = settings.opentok.broadcasts.find settings.broadcast_id
66
+ redirect broadcast.broadcastUrls['hls'] if broadcast.status == 'started'
67
+ end
68
+
69
+ get '/stop/:broadcastId' do
70
+ broadcast = settings.opentok.broadcasts.stop settings.broadcast_id
71
+ settings.broadcast_id = nil
72
+ settings.focus_stream_id = ''
73
+ settings.broadcast_layout = 'horizontalPresentation'
74
+ body broadcast.to_json
75
+ end
76
+
77
+ post '/broadcast/:broadcastId/layout' do
78
+ layoutType = params[:type]
79
+ settings.opentok.broadcasts.layout(settings.broadcast_id, type: layoutType)
80
+ settings.broadcast_layout = layoutType
81
+ end
82
+
83
+ post '/focus' do
84
+ hash = { items: [] }
85
+ hash[:items] << { id: params[:focus], layoutClassList: ['focus'] }
86
+ settings.focus_stream_id = params[:focus]
87
+ if params.key?('otherStreams')
88
+ params[:otherStreams].each do |stream|
89
+ hash[:items] << { id: stream, layoutClassList: [] }
90
+ end
91
+ end
92
+ settings.opentok.streams.layout(settings.session.session_id, hash)
93
+ end
94
+
95
+ # start the server if ruby file executed directly
96
+ run! if app_file == $0
97
+ end