opentok 4.1.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
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