diff --git a/CHANGES.md b/CHANGES.md index c13acc4..c984f5a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +# 4.6.0 + +* Adds functionality for working with the Audio Connector feature [#247](https://github.com/opentok/OpenTok-Ruby-SDK/pull/247) + # 4.5.1 * Fixes issue with uninitialized constant by adding missing `require` statement [#256](https://github.com/opentok/OpenTok-Ruby-SDK/pull/256) diff --git a/README.md b/README.md index 731cda1..ef0a16c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The OpenTok Ruby SDK provides methods for: * [Disconnecting clients from sessions](https://tokbox.com/developer/guides/moderation/rest/) * [Forcing clients in a session to disconnect or mute published audio](https://tokbox.com/developer/guides/moderation/) * Working with OpenTok [Experience Composers](https://tokbox.com/developer/guides/experience-composer) +* Working with OpenTok [Audio Connector](https://tokbox.com/developer/guides/audio-connector) ## Installation @@ -188,7 +189,10 @@ archive = opentok.archives.create session_id :output_mode => :individual The `:output_mode => :composed` setting (the default) causes all streams in the archive to be recorded to a single (composed) file. -For composed archives you can set the resolution of the archive, either "640x480" (SD landscape, the default), "1280x720" (HD landscape), "1920x1080" (FHD landscape), "480x640" (SD portrait), "720x1280" (HD portrait), or "1080x1920" (FHD portrait).. The `resolution` parameter is optional and could be included in the options +For composed archives you can set the resolution of the archive, either "640x480" +(SD landscape, the default), "1280x720" (HD landscape), "1920x1080" (FHD landscape), +"480x640" (SD portrait), "720x1280" (HD portrait), or "1080x1920" (FHD portrait). +The `resolution` parameter is optional and could be included in the options hash (second argument) of the `opentok.archives.create()` method. ```ruby @@ -322,7 +326,7 @@ For more information on archiving, see the ### Signaling -You can send a signal using the `opentok.signals.send(session_id, connection_id, opts)` method. +You can send a signal using the `opentok.signals.send(session_id, connection_id, opts)` method. If `connection_id` is nil or an empty string, then the signal is send to all valid connections in the session. @@ -457,7 +461,7 @@ You can cause a client to be forced to disconnect from a session by using the ### Forcing clients in a session to mute published audio -You can force the publisher of a specific stream to stop publishing audio using the +You can force the publisher of a specific stream to stop publishing audio using the `opentok.streams.force_mute(session_id, stream_id)` method. You can force the publisher of all streams in a session (except for an optional list of streams) @@ -495,6 +499,11 @@ You can stop an Experience Composer by calling the `opentok.renders.stop(render_ You can get information about Experience Composers by calling the `opentok.renders.find(render_id)` and `opentok.renders.list(options)` methods. +### Working with Audio Connector + +You can start an [Audio Connector](https://tokbox.com/developer/guides/audio-connector) WebSocket +by calling the `opentok.websocket.connect()` method. + ## Samples There are three sample applications included in this repository. To get going as fast as possible, clone the whole diff --git a/lib/opentok/client.rb b/lib/opentok/client.rb index 20b6845..862a40a 100644 --- a/lib/opentok/client.rb +++ b/lib/opentok/client.rb @@ -298,6 +298,35 @@ def signal(session_id, connection_id, opts) raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}" end + def connect_websocket(session_id, token, websocket_uri, opts) + opts.extend(HashExtensions) + body = { "sessionId" => session_id, + "token" => token, + "websocket" => { "uri" => websocket_uri }.merge(opts.camelize_keys!) + } + + response = self.class.post("/v2/project/#{@api_key}/connect", { + :body => body.to_json, + :headers => generate_headers("Content-Type" => "application/json") + }) + case response.code + when 200 + response + when 400 + raise ArgumentError, "One of the properties is invalid." + when 403 + raise OpenTokAuthenticationError, "You are not authorized to start the call, check your authentication information." + when 409 + raise OpenTokWebSocketError, "Conflict. Only routed sessions are allowed to initiate Connect Calls." + when 500 + raise OpenTokError, "OpenTok server error." + else + raise OpenTokWebSocketError, "The WebSocket could not be connected" + end + rescue StandardError => e + raise OpenTokError, "Failed to connect to OpenTok. Response code: #{e.message}" + end + def dial(session_id, token, sip_uri, opts) opts.extend(HashExtensions) body = { "sessionId" => session_id, diff --git a/lib/opentok/exceptions.rb b/lib/opentok/exceptions.rb index e3fde40..0986f98 100644 --- a/lib/opentok/exceptions.rb +++ b/lib/opentok/exceptions.rb @@ -14,7 +14,8 @@ class OpenTokConnectionError < OpenTokError; end class OpenTokStreamLayoutError < OpenTokError; end # Defines errors raised when you perform Broadcast operations. class OpenTokBroadcastError < OpenTokError; end + # Defines errors raised when connecting to WebSocket URIs. + class OpenTokWebSocketError < OpenTokError; end # Defines errors raised when you perform Experience Composer render operations. class OpenTokRenderError < OpenTokError; end - end diff --git a/lib/opentok/opentok.rb b/lib/opentok/opentok.rb index 5fe26ed..2a7e58a 100644 --- a/lib/opentok/opentok.rb +++ b/lib/opentok/opentok.rb @@ -218,6 +218,11 @@ def connections @connections ||= Connections.new client end + # A WebSocket object, which lets you connect OpenTok streams to a WebSocket URI. + def websocket + @websocket ||= WebSocket.new client + end + protected def client @client ||= Client.new api_key, api_secret, api_url, ua_addendum, timeout_length: @timeout_length diff --git a/lib/opentok/version.rb b/lib/opentok/version.rb index 641e8e2..70bc320 100644 --- a/lib/opentok/version.rb +++ b/lib/opentok/version.rb @@ -1,4 +1,4 @@ module OpenTok # @private - VERSION = '4.5.1' + VERSION = '4.6.0' end diff --git a/lib/opentok/websocket.rb b/lib/opentok/websocket.rb new file mode 100644 index 0000000..875487b --- /dev/null +++ b/lib/opentok/websocket.rb @@ -0,0 +1,37 @@ +require "opentok/client" + +# An object that lets you work with Audio Connector WebSocket connections. +module OpenTok + class WebSocket + # Starts an Audio Connector WebSocket connection to send audio from a Vonage Video API session to a WebSocket URI. + # See the {https://tokbox.com/developer/guides/audio-connector/ OpenTok Audio Connector developer guide}. + # + # @example + # opts = { + # "streams" => ["STREAMID1", "STREAMID2"], + # "headers" => { + # "key1" => "val1", + # "key2" => "val2" + # } + # } + # response = opentok.websocket.connect(SESSIONID, TOKEN, "ws://service.com/wsendpoint", opts) + # + # @param [String] session_id (required) The OpenTok session ID that includes the OpenTok streams you want to include in + # the WebSocket stream. + # @param [String] token (required) The OpenTok token to be used for the Audio Connector connection to the. OpenTok session. + # @param [String] websocket_uri (required) A publicly reachable WebSocket URI to be used for the destination of the audio + # stream (such as "wss://service.com/ws-endpoint"). + # @param [Hash] opts (optional) A hash defining options for the Audio Connector WebSocket connection. For example: + # @option opts [Array] :streams (optional) An array of stream IDs for the OpenTok streams you want to include in the WebSocket stream. + # If you omit this property, all streams in the session will be included. + # @option opts [Hash] :headers (optional) A hash of key-value pairs of headers to be sent to your WebSocket server with each message, + # with a maximum length of 512 bytes. + def connect(session_id, token, websocket_uri, opts = {}) + response = @client.connect_websocket(session_id, token, websocket_uri, opts) + end + + def initialize(client) + @client = client + end + end +end diff --git a/spec/cassettes/OpenTok_WebSocket/receives_a_valid_response.yml b/spec/cassettes/OpenTok_WebSocket/receives_a_valid_response.yml new file mode 100644 index 0000000..b0cc789 --- /dev/null +++ b/spec/cassettes/OpenTok_WebSocket/receives_a_valid_response.yml @@ -0,0 +1,37 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.opentok.com/v2/project/123456/connect + body: + encoding: UTF-8 + string: '{"sessionId":"SESSIONID","token":"TOKENID","websocket":{"uri":"ws://service.com/wsendpoint"}}' + headers: + User-Agent: + - OpenTok-Ruby-SDK/<%= version %> + X-Opentok-Auth: + - eyJpc3QiOiJwcm9qZWN0IiwiYWxnIjoiSFMyNTYifQ.eyJpc3MiOiIxMjM0NTYiLCJpYXQiOjE0OTI1MTA2NjAsImV4cCI6MTQ5MjUxMDk2MH0.BplMVhJWx4ld7KLKXqEmow6MjNPPFw9W8IHCMfeb120 + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 06 Aug 2022 18:10:28 GMT + Content-Type: + - application/json + Content-Length: + - '73' + Connection: + - keep-alive + body: + encoding: UTF-8 + string: '{"id":"2299ba24-a6de-417c-88f7-28da54a441cf","connectionId":"833a7182-61a5-49d4-baae-c324b09953af"}' + recorded_at: Tue, 18 Apr 2017 10:17:40 GMT +recorded_with: VCR 6.0.0 diff --git a/spec/opentok/websocket_spec.rb b/spec/opentok/websocket_spec.rb new file mode 100644 index 0000000..2ce8b8c --- /dev/null +++ b/spec/opentok/websocket_spec.rb @@ -0,0 +1,26 @@ +require "opentok/opentok" +require "opentok/websocket" +require "opentok/version" +require "spec_helper" + +describe OpenTok::WebSocket do + before(:each) do + now = Time.parse("2017-04-18 20:17:40 +1000") + allow(Time).to receive(:now) { now } + end + + let(:api_key) { "123456" } + let(:api_secret) { "1234567890abcdef1234567890abcdef1234567890" } + let(:session_id) { "SESSIONID" } + let(:connection_id) { "CONNID" } + let(:expiring_token) { "TOKENID" } + let(:websocket_uri) { "ws://service.com/wsendpoint" } + let(:opentok) { OpenTok::OpenTok.new api_key, api_secret } + let(:websocket) { opentok.websocket } + subject { websocket } + + it "receives a valid response", :vcr => { :erb => { :version => OpenTok::VERSION + "-Ruby-Version-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"} } do + response = websocket.connect(session_id, expiring_token, websocket_uri) + expect(response).not_to be_nil + end +end