opentok 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -0
  3. data/.github/workflows/metrics.yml +17 -0
  4. data/CHANGES.md +153 -0
  5. data/README.md +3 -1
  6. data/lib/opentok/archive.rb +50 -3
  7. data/lib/opentok/archives.rb +104 -7
  8. data/lib/opentok/broadcast.rb +44 -1
  9. data/lib/opentok/broadcast_list.rb +14 -0
  10. data/lib/opentok/broadcasts.rb +131 -13
  11. data/lib/opentok/client.rb +161 -0
  12. data/lib/opentok/connections.rb +1 -1
  13. data/lib/opentok/constants.rb +1 -0
  14. data/lib/opentok/opentok.rb +8 -8
  15. data/lib/opentok/sip.rb +40 -2
  16. data/lib/opentok/streams.rb +50 -1
  17. data/lib/opentok/token_generator.rb +1 -0
  18. data/lib/opentok/version.rb +1 -1
  19. data/opentok.gemspec +1 -1
  20. data/sample/Broadcast/README.md +42 -0
  21. data/sample/Broadcast/broadcast_sample.rb +15 -0
  22. data/sample/Broadcast/views/all.erb +46 -0
  23. data/sample/Broadcast/views/index.erb +16 -1
  24. data/spec/cassettes/OpenTok_Archives/adds_a_stream_to_an_archive.yml +37 -0
  25. data/spec/cassettes/OpenTok_Archives/removes_a_stream_from_an_archive.yml +37 -0
  26. data/spec/cassettes/OpenTok_Archives/should_create_layout_archives_with_screenshare_layout_types.yml +50 -0
  27. data/spec/cassettes/OpenTok_Broadcasts/adds_a_stream_to_a_broadcast.yml +37 -0
  28. data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_all_broadcasts.yml +192 -0
  29. data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_broadcasts_with_an_offset.yml +117 -0
  30. data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_count_number_of_broadcasts.yml +92 -0
  31. data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_part_of_the_broadcasts_when_using_offset_and_count.yml +142 -0
  32. data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_session_broadcasts.yml +117 -0
  33. data/spec/cassettes/OpenTok_Broadcasts/removes_a_stream_from_a_broadcast.yml +37 -0
  34. data/spec/cassettes/OpenTok_Sip/_play_dtmf_to_connection/returns_a_200_response_code_when_passed_a_valid_dtmf_digit_string.yml +39 -0
  35. data/spec/cassettes/OpenTok_Sip/_play_dtmf_to_session/returns_a_200_response_code_when_passed_a_valid_dtmf_digit_string.yml +39 -0
  36. data/spec/cassettes/OpenTok_Sip/receives_a_valid_response.yml +2 -2
  37. data/spec/cassettes/OpenTok_Streams/disables_the_mute_state_of_a_session.yml +37 -0
  38. data/spec/cassettes/OpenTok_Streams/forces_all_current_and_future_streams_in_a_session_to_be_muted.yml +39 -0
  39. data/spec/cassettes/OpenTok_Streams/forces_all_current_and_future_streams_in_a_session_to_be_muted_except_specified_excluded_streams.yml +39 -0
  40. data/spec/cassettes/OpenTok_Streams/forces_the_specified_stream_to_be_muted.yml +39 -0
  41. data/spec/opentok/archives_spec.rb +40 -0
  42. data/spec/opentok/broadcasts_spec.rb +103 -1
  43. data/spec/opentok/connection_spec.rb +1 -1
  44. data/spec/opentok/opentok_spec.rb +6 -0
  45. data/spec/opentok/sip_spec.rb +32 -1
  46. data/spec/opentok/streams_spec.rb +21 -1
  47. metadata +42 -6
  48. data/.travis.yml +0 -17
@@ -1,6 +1,6 @@
1
1
  require "opentok/client"
2
2
  require "opentok/broadcast"
3
-
3
+ require "opentok/broadcast_list"
4
4
 
5
5
  module OpenTok
6
6
  # A class for working with OpenTok live streaming broadcasts.
@@ -22,18 +22,25 @@ module OpenTok
22
22
  # @param [String] session_id The session ID of the OpenTok session to broadcast.
23
23
  #
24
24
  # @param [Hash] options A hash defining options for the broadcast.
25
- # @option options [Hash] :layout Specify this to assign the initial layout for the broadcast.
26
- # Valid values for the layout (<code>:type</code>) property are "bestFit" (best fit), "custom" (custom),
27
- # "horizontalPresentation" (horizontal presentation), "pip" (picture-in-picture), and
28
- # "verticalPresentation" (vertical presentation)).
29
- # If you specify a (<code>:custom</code>) layout type, set the (<code>:stylesheet</code>) property of the layout object
30
- # to the stylesheet. (For other layout types, do not set a stylesheet property.)
31
- # If you do not specify an initial layout type, the broadcast stream uses the Best Fit layout type.
25
+ #
26
+ # @option options [Hash] :layout Specify this to assign the initial layout type for
27
+ # the broadcast. This is a hash containing three keys:
28
+ # <code>:type</code>, <code>:stylesheet<code> and <code>:screenshare_type</code>.
29
+ # Valid values for <code>:type</code> are "bestFit" (best fit), "custom" (custom),
30
+ # "horizontalPresentation" (horizontal presentation),
31
+ # "pip" (picture-in-picture), and "verticalPresentation" (vertical presentation)).
32
+ # If you specify a "custom" layout type, set the <code>:stylesheet</code> key to the
33
+ # stylesheet (CSS). (For other layout types, do not set the <code>:stylesheet</code> key.)
34
+ # Valid values for <code>:screenshare_type</code> are "bestFit", "pip",
35
+ # "verticalPresentation", "horizontalPresentation". This property is optional.
36
+ # If it is specified, then the <code>:type</code> property **must** be set to "bestFit".
37
+ # If you do not specify an initial layout type, the broadcast uses the best fit
38
+ # layout type.
32
39
  #
33
40
  # @option options [int] maxDuration
34
41
  # The maximum duration for the broadcast, in seconds. The broadcast will automatically stop when
35
42
  # the maximum duration is reached. You can set the maximum duration to a value from 60 (60 seconds) to 36000 (10 hours).
36
- # The default maximum duration is 2 hours (7200 seconds).
43
+ # The default maximum duration is 4 hours (14,400 seconds).
37
44
  #
38
45
  # @option options [Hash] outputs
39
46
  # This object defines the types of broadcast streams you want to start (both HLS and RTMP).
@@ -48,11 +55,21 @@ module OpenTok
48
55
  # @option options [string] resolution
49
56
  # The resolution of the broadcast: either "640x480" (SD, the default) or "1280x720" (HD).
50
57
  #
58
+ # @option options [String] :streamMode (Optional) Whether streams included in the broadcast are selected
59
+ # automatically ("auto", the default) or manually ("manual"). When streams are selected automatically ("auto"),
60
+ # all streams in the session can be included in the broadcast. When streams are selected manually ("manual"),
61
+ # you specify streams to be included based on calls to this REST method
62
+ # { https://tokbox.com/developer/rest/#selecting-broadcast-streams }. You can specify whether a
63
+ # stream's audio, video, or both are included in the broadcast.
64
+ # For both automatic and manual modes, the broadcast composer includes streams based
65
+ # on stream prioritization rules { https://tokbox.com/developer/guides/archive-broadcast-layout/#stream-prioritization-rules }.
66
+ # Important: this feature is currently available in the Standard environment only.
67
+ #
51
68
  # @return [Broadcast] The broadcast object, which includes properties defining the broadcast,
52
69
  # including the broadcast ID.
53
70
  #
54
71
  # @raise [OpenTokBroadcastError] The broadcast could not be started. The request was invalid or broadcast already started
55
- # @raise [OpenTokAuthenticationError] Authentication failed while starting an archive.
72
+ # @raise [OpenTokAuthenticationError] Authentication failed while starting an broadcast.
56
73
  # Invalid API key.
57
74
  # @raise [OpenTokError] OpenTok server error.
58
75
  def create(session_id, options = {})
@@ -78,6 +95,25 @@ module OpenTok
78
95
  Broadcast.new self, broadcast_json
79
96
  end
80
97
 
98
+ # Returns a BroadcastList, which is an array of broadcasts that are completed and in-progress,
99
+ # for your API key.
100
+ #
101
+ # @param [Hash] options A hash with keys defining which range of broadcasts to retrieve.
102
+ # @option options [integer] :offset Optional. The index offset of the first broadcast. 0 is offset
103
+ # of the most recently started broadcast. 1 is the offset of the broadcast that started prior to
104
+ # the most recent broadcast. If you do not specify an offset, 0 is used.
105
+ # @option options [integer] :count Optional. The number of broadcasts to be returned. The maximum
106
+ # number of broadcasts returned is 1000.
107
+ # @option options [String] :session_id Optional. The session ID that broadcasts belong to.
108
+ # https://tokbox.com/developer/rest/#list_broadcasts
109
+ #
110
+ # @return [BroadcastList] An BroadcastList object, which is an array of Broadcast objects.
111
+ def all(options = {})
112
+ raise ArgumentError, "Limit is invalid" unless options[:count].nil? || (0..1000).include?(options[:count])
113
+
114
+ broadcast_list_json = @client.list_broadcasts(options[:offset], options[:count], options[:sessionId])
115
+ BroadcastList.new self, broadcast_list_json
116
+ end
81
117
 
82
118
  # Stops an OpenTok broadcast
83
119
  #
@@ -104,7 +140,7 @@ module OpenTok
104
140
  # @param [String] broadcast_id
105
141
  # The broadcast ID.
106
142
  #
107
- # @option options [String] :type
143
+ # @option options [String] :type
108
144
  # The layout type. Set this to "bestFit", "pip", "verticalPresentation",
109
145
  # "horizontalPresentation", "focus", or "custom".
110
146
  #
@@ -112,6 +148,11 @@ module OpenTok
112
148
  # The stylesheet for a custom layout. Set this parameter
113
149
  # if you set <code>type</code> to <code>"custom"</code>. Otherwise, leave it undefined.
114
150
  #
151
+ # @option options [String] :screenshare_type
152
+ # The screenshare layout type. Set this to "bestFit", "pip", "verticalPresentation" or
153
+ # "horizonalPresentation". If this is defined, then the <code>type</code> parameter
154
+ # must be set to <code>"bestFit"</code>.
155
+ #
115
156
  # @raise [OpenTokBroadcastError]
116
157
  # The broadcast layout could not be updated.
117
158
  #
@@ -137,13 +178,90 @@ module OpenTok
137
178
  raise ArgumentError, "broadcast_id not provided" if broadcast_id.to_s.empty?
138
179
  type = options[:type]
139
180
  raise ArgumentError, "custom type must have a stylesheet" if (type.eql? "custom") && (!options.key? :stylesheet)
140
- valid_non_custom_type = ["bestFit","horizontalPresentation","pip", "verticalPresentation", ""].include? type
141
- raise ArgumentError, "type is not valid" if !valid_non_custom_type
181
+ valid_non_custom_layouts = ["bestFit","horizontalPresentation","pip", "verticalPresentation", ""]
182
+ valid_non_custom_type = valid_non_custom_layouts.include? type
183
+ raise ArgumentError, "type is not valid" if !valid_non_custom_type && !(type.eql? "custom")
142
184
  raise ArgumentError, "stylesheet not needed" if valid_non_custom_type and options.key? :stylesheet
185
+ raise ArgumentError, "screenshare_type is not valid" if options[:screenshare_type] && !valid_non_custom_layouts.include?(options[:screenshare_type])
186
+ raise ArgumentError, "type must be set to 'bestFit' if screenshare_type is defined" if options[:screenshare_type] && type != 'bestFit'
143
187
  response = @client.layout_broadcast(broadcast_id, options)
144
188
  (200..300).include? response.code
145
189
  end
146
190
 
191
+ # Adds a stream to currently running broadcast that was started with the
192
+ # streamMode set to "manual". For a description of the feature, see
193
+ # {https://tokbox.com/developer/rest/#selecting-broadcast-streams}.
194
+ #
195
+ # @param [String] broadcast_id
196
+ # The broadcast ID.
197
+ #
198
+ # @param [String] stream_id
199
+ # The ID for the stream to be added to the broadcast
200
+ #
201
+ # @option opts [true, false] :has_audio
202
+ # (Boolean, optional) — Whether the broadcast should include the stream's
203
+ # audio (true, the default) or not (false).
204
+ #
205
+ # @option opts [true, false] :has_video
206
+ # (Boolean, optional) — Whether the broadcast should include the stream's
207
+ # video (true, the default) or not (false).
208
+ #
209
+ # You can call the method repeatedly with add_stream set to the same stream ID, to
210
+ # toggle the stream's audio or video in the broadcast. If you set both has_audio and
211
+ # has_video to false, you will get error response.
212
+ #
213
+ # @raise [ArgumentError]
214
+ # The broadcast_id parameter is empty.
215
+ #
216
+ # @raise [ArgumentError]
217
+ # The stream_id parameter is empty.
218
+ #
219
+ # @raise [ArgumentError]
220
+ # The has_audio and has_video properties of the options parameter are both set to "false"
221
+ #
222
+ def add_stream(broadcast_id, stream_id, options)
223
+ raise ArgumentError, "broadcast_id not provided" if broadcast_id.to_s.empty?
224
+ raise ArgumentError, "stream_id not provided" if stream_id.to_s.empty?
225
+ if options.has_key?(:has_audio) && options.has_key?(:has_video)
226
+ has_audio = options[:has_audio]
227
+ has_video = options[:has_video]
228
+ raise ArgumentError, "has_audio and has_video can't both be false" if audio_and_video_options_both_false?(has_audio, has_video)
229
+ end
230
+ options['add_stream'] = stream_id
231
+
232
+ @client.select_streams_for_broadcast(broadcast_id, options)
233
+ end
234
+
235
+ # Removes a stream from a currently running broadcast that was started with the
236
+ # streamMode set to "manual". For a description of the feature, see
237
+ # {https://tokbox.com/developer/rest/#selecting-broadcast-streams}.
238
+ #
239
+ # @param [String] broadcast_id
240
+ # The broadcast ID.
241
+ #
242
+ # @param [String] stream_id
243
+ # The ID for the stream to be removed from the broadcast
244
+ #
245
+ # @raise [ArgumentError]
246
+ # The broadcast_id parameter id is empty.
247
+ #
248
+ # @raise [ArgumentError]
249
+ # The stream_id parameter is empty.
250
+ #
251
+ def remove_stream(broadcast_id, stream_id)
252
+ raise ArgumentError, "broadcast_id not provided" if broadcast_id.to_s.empty?
253
+ raise ArgumentError, "stream_id not provided" if stream_id.to_s.empty?
254
+ options = {}
255
+ options['remove_stream'] = stream_id
256
+
257
+ @client.select_streams_for_broadcast(broadcast_id, options)
258
+ end
259
+
260
+ private
261
+
262
+ def audio_and_video_options_both_false?(has_audio, has_video)
263
+ has_audio == false && has_video == false
264
+ end
147
265
 
148
266
  end
149
267
  end
@@ -188,6 +188,33 @@ module OpenTok
188
188
  raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
189
189
  end
190
190
 
191
+ def select_streams_for_archive(archive_id, opts)
192
+ opts.extend(HashExtensions)
193
+ body = opts.camelize_keys!
194
+ response = self.class.patch("/v2/project/#{@api_key}/archive/#{archive_id}/streams", {
195
+ :body => body.to_json,
196
+ :headers => generate_headers("Content-Type" => "application/json")
197
+ })
198
+ case response.code
199
+ when 204
200
+ response
201
+ when 400
202
+ raise OpenTokArchiveError, "The request was invalid."
203
+ when 403
204
+ raise OpenTokAuthenticationError, "Authentication failed. API Key: #{@api_key}"
205
+ when 404
206
+ raise OpenTokArchiveError, "No matching archive found with the specified ID: #{archive_id}"
207
+ when 405
208
+ raise OpenTokArchiveError, "The archive was started with streamMode set to 'auto', which does not support stream manipulation."
209
+ when 500
210
+ raise OpenTokError, "OpenTok server error."
211
+ else
212
+ raise OpenTokArchiveError, "The archive streams could not be updated."
213
+ end
214
+ rescue StandardError => e
215
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
216
+ end
217
+
191
218
  def forceDisconnect(session_id, connection_id)
192
219
  response = self.class.delete("/v2/project/#{@api_key}/session/#{session_id}/connection/#{connection_id}", {
193
220
  :headers => generate_headers("Content-Type" => "application/json")
@@ -206,6 +233,45 @@ module OpenTok
206
233
  raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
207
234
  end
208
235
 
236
+ def force_mute_stream(session_id, stream_id)
237
+ response = self.class.post("/v2/project/#{@api_key}/session/#{session_id}/stream/#{stream_id}/mute", {
238
+ :headers => generate_headers("Content-Type" => "application/json")
239
+ })
240
+ case response.code
241
+ when 200
242
+ response
243
+ when 400
244
+ raise ArgumentError, "Force mute failed. Stream ID #{stream_id} or Session ID #{session_id} is invalid"
245
+ when 403
246
+ raise OpenTokAuthenticationError, "Authentication failed. API Key: #{@api_key}"
247
+ when 404
248
+ raise OpenTokConnectionError, "Either Stream ID #{stream_id} or Session ID #{session_id} is invalid"
249
+ end
250
+ rescue StandardError => e
251
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
252
+ end
253
+
254
+ def force_mute_session(session_id, opts)
255
+ opts.extend(HashExtensions)
256
+ body = opts.camelize_keys!
257
+ response = self.class.post("/v2/project/#{@api_key}/session/#{session_id}/mute", {
258
+ :body => body.to_json,
259
+ :headers => generate_headers("Content-Type" => "application/json")
260
+ })
261
+ case response.code
262
+ when 200
263
+ response
264
+ when 400
265
+ raise ArgumentError, "Force mute failed. The request could not be processed due to a bad request"
266
+ when 403
267
+ raise OpenTokAuthenticationError, "Authentication failed. API Key: #{@api_key}"
268
+ when 404
269
+ raise OpenTokConnectionError, "Session ID #{session_id} is invalid"
270
+ end
271
+ rescue StandardError => e
272
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
273
+ end
274
+
209
275
  def signal(session_id, connection_id, opts)
210
276
  opts.extend(HashExtensions)
211
277
  connectionPath = connection_id.to_s.empty? ? "" : "/connection/#{connection_id}"
@@ -257,6 +323,52 @@ module OpenTok
257
323
  raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
258
324
  end
259
325
 
326
+ def play_dtmf_to_connection(session_id, connection_id, dtmf_digits)
327
+ body = { "digits" => dtmf_digits }
328
+
329
+ response = self.class.post("/v2/project/#{@api_key}/session/#{session_id}/connection/#{connection_id}/play-dtmf", {
330
+ :body => body.to_json,
331
+ :headers => generate_headers("Content-Type" => "application/json")
332
+ })
333
+ case response.code
334
+ when 200
335
+ response
336
+ when 400
337
+ raise ArgumentError, "One of the properties — dtmf_digits #{dtmf_digits} or session_id #{session_id} — is invalid."
338
+ when 403
339
+ raise OpenTokAuthenticationError, "Authentication failed. This can occur if you use an invalid OpenTok API key or an invalid JSON web token. API Key: #{@api_key}"
340
+ when 404
341
+ raise OpenTokError, "The specified session #{session_id} does not exist or the client specified by the #{connection_id} property is not connected to the session."
342
+ else
343
+ raise OpenTokError, "An error occurred when attempting to play DTMF digits to the session"
344
+ end
345
+ rescue StandardError => e
346
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
347
+ end
348
+
349
+ def play_dtmf_to_session(session_id, dtmf_digits)
350
+ body = { "digits" => dtmf_digits }
351
+
352
+ response = self.class.post("/v2/project/#{@api_key}/session/#{session_id}/play-dtmf", {
353
+ :body => body.to_json,
354
+ :headers => generate_headers("Content-Type" => "application/json")
355
+ })
356
+ case response.code
357
+ when 200
358
+ response
359
+ when 400
360
+ raise ArgumentError, "One of the properties — dtmf_digits #{dtmf_digits} or session_id #{session_id} — is invalid."
361
+ when 403
362
+ raise OpenTokAuthenticationError, "Authentication failed. This can occur if you use an invalid OpenTok API key or an invalid JSON web token. API Key: #{@api_key}"
363
+ when 404
364
+ raise OpenTokError, "The specified session does not exist. Session ID: #{session_id}"
365
+ else
366
+ raise OpenTokError, "An error occurred when attempting to play DTMF digits to the session"
367
+ end
368
+ rescue StandardError => e
369
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
370
+ end
371
+
260
372
  def info_stream(session_id, stream_id)
261
373
  streamId = stream_id.to_s.empty? ? '' : "/#{stream_id}"
262
374
  url = "/v2/project/#{@api_key}/session/#{session_id}/stream#{streamId}"
@@ -371,6 +483,28 @@ module OpenTok
371
483
  raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
372
484
  end
373
485
 
486
+ def list_broadcasts(offset, count, session_id)
487
+ query = Hash.new
488
+ query[:offset] = offset unless offset.nil?
489
+ query[:count] = count unless count.nil?
490
+ query[:sessionId] = session_id unless session_id.nil?
491
+ response = self.class.get("/v2/project/#{@api_key}/broadcast", {
492
+ :query => query.empty? ? nil : query,
493
+ :headers => generate_headers,
494
+ })
495
+ case response.code
496
+ when 200
497
+ response
498
+ when 403
499
+ raise OpenTokAuthenticationError,
500
+ "Authentication failed while retrieving broadcasts. API Key: #{@api_key}"
501
+ else
502
+ raise OpenTokBroadcastError, "The broadcasts could not be retrieved."
503
+ end
504
+ rescue StandardError => e
505
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
506
+ end
507
+
374
508
  def layout_broadcast(broadcast_id, opts)
375
509
  opts.extend(HashExtensions)
376
510
  response = self.class.put("/v2/project/#{@api_key}/broadcast/#{broadcast_id}/layout", {
@@ -393,5 +527,32 @@ module OpenTok
393
527
  raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
394
528
  end
395
529
 
530
+ def select_streams_for_broadcast(broadcast_id, opts)
531
+ opts.extend(HashExtensions)
532
+ body = opts.camelize_keys!
533
+ response = self.class.patch("/v2/project/#{@api_key}/broadcast/#{broadcast_id}/streams", {
534
+ :body => body.to_json,
535
+ :headers => generate_headers("Content-Type" => "application/json")
536
+ })
537
+ case response.code
538
+ when 204
539
+ response
540
+ when 400
541
+ raise OpenTokBroadcastError, "The request was invalid."
542
+ when 403
543
+ raise OpenTokAuthenticationError, "Authentication failed. API Key: #{@api_key}"
544
+ when 404
545
+ raise OpenTokBroadcastError, "No matching broadcast found with the specified ID: #{broadcast_id}"
546
+ when 405
547
+ raise OpenTokBroadcastError, "The broadcast was started with streamMode set to 'auto', which does not support stream manipulation."
548
+ when 500
549
+ raise OpenTokError, "OpenTok server error."
550
+ else
551
+ raise OpenTokBroadcastError, "The broadcast streams could not be updated."
552
+ end
553
+ rescue StandardError => e
554
+ raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}"
555
+ end
556
+
396
557
  end
397
558
  end
@@ -25,4 +25,4 @@ module OpenTok
25
25
  end
26
26
 
27
27
  end
28
- end
28
+ end
@@ -1,4 +1,5 @@
1
1
  module OpenTok
2
+ require "set"
2
3
  API_URL = "https://api.opentok.com"
3
4
  TOKEN_SENTINEL = "T1=="
4
5
  ROLES = { subscriber: "subscriber", publisher: "publisher", moderator: "moderator" }
@@ -1,3 +1,6 @@
1
+ require "resolv"
2
+ require "set"
3
+
1
4
  require "opentok/constants"
2
5
  require "opentok/session"
3
6
  require "opentok/client"
@@ -9,9 +12,6 @@ require "opentok/streams"
9
12
  require "opentok/signals"
10
13
  require "opentok/broadcasts"
11
14
 
12
- require "resolv"
13
- require "set"
14
-
15
15
  module OpenTok
16
16
  # Contains methods for creating OpenTok sessions and generating tokens. It also includes
17
17
  # methods for returning object that let you work with archives, work with live streaming
@@ -42,9 +42,9 @@ module OpenTok
42
42
  # streams, and signal. (This is the default value if you do not specify a role.)
43
43
  #
44
44
  # * <code>:moderator</code> -- In addition to the privileges granted to a
45
- # publisher, in clients using the OpenTok.js library, a moderator can call the
46
- # <code>forceUnpublish()</code> and <code>forceDisconnect()</code> method of the
47
- # Session object.
45
+ # publisher, a moderator can perform moderation functions, such as forcing clients
46
+ # to disconnect, to stop publishing streams, or to mute audio in published streams. See the
47
+ # {https://tokbox.com/developer/guides/moderation/ Moderation developer guide}.
48
48
  # @option options [integer] :expire_time The expiration time, in seconds since the UNIX epoch.
49
49
  # Pass in 0 to use the default expiration time of 24 hours after the token creation time.
50
50
  # The maximum expiration time is 30 days after the creation time.
@@ -207,14 +207,14 @@ module OpenTok
207
207
  @signals ||= Signals.new client
208
208
  end
209
209
 
210
- # A Connections object, which lets disconnect clients from an OpenTok session.
210
+ # A Connections object, which lets you disconnect clients from an OpenTok session.
211
211
  def connections
212
212
  @connections ||= Connections.new client
213
213
  end
214
214
 
215
215
  protected
216
216
  def client
217
- @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
218
218
  end
219
219
 
220
220
  end
data/lib/opentok/sip.rb CHANGED
@@ -12,7 +12,9 @@ module OpenTok
12
12
  # "password" => sip_password },
13
13
  # "headers" => { "X-KEY1" => "value1",
14
14
  # "X-KEY1" => "value2" },
15
- # "secure" => "true"
15
+ # "secure" => "true",
16
+ # "video" => "true",
17
+ # "observe_force_mute" => "true"
16
18
  # }
17
19
  # response = opentok.sip.dial(session_id, token, "sip:+15128675309@acme.pstn.example.com;transport=tls", opts)
18
20
  # @param [String] session_id The session ID corresponding to the session to which
@@ -33,14 +35,50 @@ module OpenTok
33
35
  # @option opts [Hash] :auth This object contains the username and password
34
36
  # to be used in the the SIP INVITE​ request for HTTP digest authentication,
35
37
  # if it is required by your SIP platform.
36
- # @option opts [true, false] :secure Wether the media must be transmitted
38
+ # @option opts [true, false] :secure Whether the media must be transmitted
37
39
  # encrypted (​true​) or not (​false​, the default).
40
+ # @option opts [true, false] :video Whether the SIP call will include
41
+ # video (​true​) or not (​false​, the default). With video included, the SIP
42
+ # client's video is included in the OpenTok stream that is sent to the
43
+ # OpenTok session. The SIP client will receive a single composed video of
44
+ # the published streams in the OpenTok session.
45
+ # @option opts [true, false] :observe_force_mute Whether the SIP end point
46
+ # observes {https://tokbox.com/developer/guides/moderation/#force_mute force mute moderation}
47
+ # (true) or not (false, the default).
38
48
  def dial(session_id, token, sip_uri, opts)
39
49
  response = @client.dial(session_id, token, sip_uri, opts)
40
50
  end
41
51
 
52
+ # Sends DTMF digits to a specific client connected to an OpnTok session.
53
+ #
54
+ # @param [String] session_id The session ID.
55
+ # @param [String] connection_id The connection ID of the specific connection that
56
+ # the DTMF signal is being sent to.
57
+ # @param [String] dtmf_digits The DTMF digits to send. This can include 0-9, "*", "#", and "p".
58
+ # A p indicates a pause of 500ms (if you need to add a delay in sending the digits).
59
+ def play_dtmf_to_connection(session_id, connection_id, dtmf_digits)
60
+ raise ArgumentError, "invalid DTMF digits" unless dtmf_digits_valid?(dtmf_digits)
61
+ response = @client.play_dtmf_to_connection(session_id, connection_id, dtmf_digits)
62
+ end
63
+
64
+ # Sends DTMF digits to all clients connected to an OpnTok session.
65
+ #
66
+ # @param [String] session_id The session ID.
67
+ # @param [String] dtmf_digits The DTMF digits to send. This can include 0-9, "*", "#", and "p".
68
+ # A p indicates a pause of 500ms (if you need to add a delay in sending the digits).
69
+ def play_dtmf_to_session(session_id, dtmf_digits)
70
+ raise ArgumentError, "invalid DTMF digits" unless dtmf_digits_valid?(dtmf_digits)
71
+ response = @client.play_dtmf_to_session(session_id, dtmf_digits)
72
+ end
73
+
42
74
  def initialize(client)
43
75
  @client = client
44
76
  end
77
+
78
+ private
79
+
80
+ def dtmf_digits_valid?(dtmf_digits)
81
+ dtmf_digits.match?(/^[0-9*#p]+$/)
82
+ end
45
83
  end
46
84
  end
@@ -75,5 +75,54 @@ module OpenTok
75
75
  (200..300).include? response.code
76
76
  end
77
77
 
78
+ # Force a specific stream connected to an OpenTok session to mute itself.
79
+ #
80
+ # @param [String] session_id The session ID of the OpenTok session.
81
+ # @param [String] stream_id The stream ID of the stream in the session.
82
+ #
83
+ def force_mute(session_id, stream_id)
84
+ response = @client.force_mute_stream(session_id, stream_id)
85
+ end
86
+
87
+ # Force all streams connected to an OpenTok session (except for an optional list of streams),
88
+ # to mute published audio.
89
+ #
90
+ # In addition to existing streams, any streams that are published after the call to
91
+ # this method are published with audio muted. You can remove the mute state of a session
92
+ # by calling the disable_force_mute() method.
93
+ #
94
+ # @param [String] session_id The session ID.
95
+ # @param [Hash] opts An optional hash defining options for muting action. For example:
96
+ # @option opts [Array] :excluded_streams The stream IDs for streams that should not be muted.
97
+ # This is an optional property. If you omit this property, all streams in the session will be muted.
98
+ #
99
+ # @example
100
+ # {
101
+ # "excluded_streams" => [
102
+ # "excludedStreamId1",
103
+ # "excludedStreamId2"
104
+ # ]
105
+ # }
106
+ #
107
+ def force_mute_all(session_id, opts = {})
108
+ opts['active'] = 'true'
109
+ response = @client.force_mute_session(session_id, opts)
110
+ end
111
+
112
+ # Disables the active mute state of the session. After you call this method, new streams
113
+ # published to the session will no longer have audio muted.
114
+ #
115
+ # After you call the force_mute_all() method, any streams published after
116
+ # the call are published with audio muted. Call the disable_force_mute() method
117
+ # to remove the mute state of a session, so that new published streams are not
118
+ # automatically muted.
119
+ #
120
+ # @param [String] session_id The session ID.
121
+ #
122
+ def disable_force_mute(session_id)
123
+ opts = {'active' => 'false'}
124
+ response = @client.force_mute_session(session_id, opts)
125
+ end
126
+
78
127
  end
79
- end
128
+ end
@@ -4,6 +4,7 @@ require "opentok/session"
4
4
  require "base64"
5
5
  require "addressable/uri"
6
6
  require "openssl"
7
+ require "active_support"
7
8
  require "active_support/time"
8
9
 
9
10
  module OpenTok
@@ -1,4 +1,4 @@
1
1
  module OpenTok
2
2
  # @private
3
- VERSION = '4.1.0'
3
+ VERSION = '4.3.0'
4
4
  end
data/opentok.gemspec CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  bundler_version = RUBY_VERSION < '2.1' ? '~> 1.5' : '>= 1.5'
20
20
  spec.add_development_dependency "bundler", bundler_version
21
- spec.add_development_dependency "rake", "~> 12.0.0"
21
+ spec.add_development_dependency "rake", "~> 12.3.3"
22
22
  spec.add_development_dependency "rspec", "~> 3.9.0"
23
23
  spec.add_development_dependency "webmock", ">= 2.3.2"
24
24
  spec.add_development_dependency "vcr", ">= 2.8.0"
@@ -168,6 +168,48 @@ You will now see the participant in the broadcast.
168
168
  end
169
169
  ```
170
170
 
171
+ ### All broadcasts
172
+
173
+ Start by visiting the list page at <http://localhost:4567/all>. You will see a table that
174
+ displays all the broadcasts created with your API Key. If there are more than five, the next ones
175
+ can be seen by clicking the "Next →" link. Some basic information like
176
+ when the broadcast was created, how long it is, and its status is also shown. You should see the
177
+ broadcasts you created in the previous sections here.
178
+
179
+ We begin to see how this page is created by looking at the route handler for this URL:
180
+
181
+ ```ruby
182
+ get '/all' do
183
+ page = (params[:page] || "1").to_i
184
+ offset = (page - 1) * 5
185
+ broadcasts = settings.opentok.broadcasts.all(:offset => offset, :count => 5)
186
+
187
+ show_previous = page > 1 ? '/all?page=' + (page-1).to_s : nil
188
+ show_next = broadcasts.total > (offset + 5) ? '/all?page=' + (page+1).to_s : nil
189
+
190
+ erb :all, :locals => {
191
+ :broadcasts => broadcasts,
192
+ :show_previous => show_previous,
193
+ :show_next => show_next
194
+ }
195
+ end
196
+ ```
197
+
198
+ This view is paginated so that we don't potentially show hundreds of rows on the table, which would
199
+ be difficult for the user to navigate. So this code starts by figuring out which page needs to be
200
+ shown, where each page is a set of 5 broadcasts. The `page` number is read from the request's query
201
+ string parameters as a string and then converted into an Integer. The `offset`, which represents how
202
+ many broadcasts are being skipped is always calculated as five times as many pages that are less than
203
+ the current page, which is `(page - 1) * 5`. Now there is enough information to ask for a list of
204
+ broadcasts from OpenTok, which we do by calling the `broadcasts.all()` method of the `opentok` instance.
205
+ The parameter is an optional Hash that contains the offset, the count (which is always 5 in this
206
+ view). If we are not at the first page, we can pass the view a string that contains the relative URL
207
+ for the previous page. Similarly, we can also include one for the next page. Now the application
208
+ renders the view using that information and the partial list of broadcasts.
209
+
210
+ At this point the template file `views/all.erb` handles looping over the array of broadcasts and
211
+ outputting the proper information for each column in the table.
212
+
171
213
  ### Changing the layout classes for streams
172
214
 
173
215
  In the host page, if you click on either the host or a participant video, that video gets