opentok 4.1.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -0
- data/.github/workflows/metrics.yml +17 -0
- data/CHANGES.md +153 -0
- data/README.md +3 -1
- data/lib/opentok/archive.rb +50 -3
- data/lib/opentok/archives.rb +104 -7
- data/lib/opentok/broadcast.rb +44 -1
- data/lib/opentok/broadcast_list.rb +14 -0
- data/lib/opentok/broadcasts.rb +131 -13
- data/lib/opentok/client.rb +161 -0
- data/lib/opentok/connections.rb +1 -1
- data/lib/opentok/constants.rb +1 -0
- data/lib/opentok/opentok.rb +8 -8
- data/lib/opentok/sip.rb +40 -2
- data/lib/opentok/streams.rb +50 -1
- data/lib/opentok/token_generator.rb +1 -0
- data/lib/opentok/version.rb +1 -1
- data/opentok.gemspec +1 -1
- data/sample/Broadcast/README.md +42 -0
- data/sample/Broadcast/broadcast_sample.rb +15 -0
- data/sample/Broadcast/views/all.erb +46 -0
- data/sample/Broadcast/views/index.erb +16 -1
- data/spec/cassettes/OpenTok_Archives/adds_a_stream_to_an_archive.yml +37 -0
- data/spec/cassettes/OpenTok_Archives/removes_a_stream_from_an_archive.yml +37 -0
- data/spec/cassettes/OpenTok_Archives/should_create_layout_archives_with_screenshare_layout_types.yml +50 -0
- data/spec/cassettes/OpenTok_Broadcasts/adds_a_stream_to_a_broadcast.yml +37 -0
- data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_all_broadcasts.yml +192 -0
- data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_broadcasts_with_an_offset.yml +117 -0
- data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_count_number_of_broadcasts.yml +92 -0
- data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_part_of_the_broadcasts_when_using_offset_and_count.yml +142 -0
- data/spec/cassettes/OpenTok_Broadcasts/for_many_broadcasts/should_return_session_broadcasts.yml +117 -0
- data/spec/cassettes/OpenTok_Broadcasts/removes_a_stream_from_a_broadcast.yml +37 -0
- data/spec/cassettes/OpenTok_Sip/_play_dtmf_to_connection/returns_a_200_response_code_when_passed_a_valid_dtmf_digit_string.yml +39 -0
- data/spec/cassettes/OpenTok_Sip/_play_dtmf_to_session/returns_a_200_response_code_when_passed_a_valid_dtmf_digit_string.yml +39 -0
- data/spec/cassettes/OpenTok_Sip/receives_a_valid_response.yml +2 -2
- data/spec/cassettes/OpenTok_Streams/disables_the_mute_state_of_a_session.yml +37 -0
- data/spec/cassettes/OpenTok_Streams/forces_all_current_and_future_streams_in_a_session_to_be_muted.yml +39 -0
- data/spec/cassettes/OpenTok_Streams/forces_all_current_and_future_streams_in_a_session_to_be_muted_except_specified_excluded_streams.yml +39 -0
- data/spec/cassettes/OpenTok_Streams/forces_the_specified_stream_to_be_muted.yml +39 -0
- data/spec/opentok/archives_spec.rb +40 -0
- data/spec/opentok/broadcasts_spec.rb +103 -1
- data/spec/opentok/connection_spec.rb +1 -1
- data/spec/opentok/opentok_spec.rb +6 -0
- data/spec/opentok/sip_spec.rb +32 -1
- data/spec/opentok/streams_spec.rb +21 -1
- metadata +42 -6
- data/.travis.yml +0 -17
data/lib/opentok/broadcasts.rb
CHANGED
@@ -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
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
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
|
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
|
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
|
-
|
141
|
-
|
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
|
data/lib/opentok/client.rb
CHANGED
@@ -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
|
data/lib/opentok/connections.rb
CHANGED
data/lib/opentok/constants.rb
CHANGED
data/lib/opentok/opentok.rb
CHANGED
@@ -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
|
-
#
|
46
|
-
#
|
47
|
-
#
|
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
|
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
|
data/lib/opentok/streams.rb
CHANGED
@@ -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
|
data/lib/opentok/version.rb
CHANGED
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.
|
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"
|
data/sample/Broadcast/README.md
CHANGED
@@ -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
|