Skip to content

Commit e924fb5

Browse files
authored
Merge pull request #312 from opentok/feature/audio-streamer-lite
Add Audio Connector for the ability to add audio to Websockets
2 parents 404f318 + 6653e91 commit e924fb5

File tree

9 files changed

+225
-20
lines changed

9 files changed

+225
-20
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The OpenTok PHP SDK provides methods for:
1616
* [Sending signals to clients connected to a session](https://tokbox.com/developer/guides/signaling/)
1717
* [Disconnecting clients from sessions](https://tokbox.com/developer/guides/moderation/rest/)
1818
* [Forcing clients in a session to disconnect or mute published audio](https://tokbox.com/developer/guides/moderation/)
19+
* Working with OpenTok [Audio Connector](https://tokbox.com/developer/guides/audio-connector)
1920

2021
## Installation
2122

@@ -474,6 +475,12 @@ $opentok->dial($sessionId, $token, $sipUri, $options);
474475
For more information, see the [OpenTok SIP Interconnect developer
475476
guide](https://tokbox.com/developer/guides/sip/).
476477

478+
### Working with Audio Connector
479+
480+
You can start an [Audio Connector](https://tokbox.com/developer/guides/audio-connector) WebSocket
481+
by calling the `connectAudio()` method of the
482+
`OpenTok\OpenTok` class.
483+
477484
## Samples
478485

479486
There are three sample applications included in this repository. To get going as fast as possible, clone the whole

src/OpenTok/OpenTok.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,40 @@ public function signal($sessionId, $payload, $connectionId = null)
11741174
}
11751175
}
11761176

1177+
/**
1178+
* Starts an <a href="https://tokbox.com/developer/guides/audio-connector/">Audio Connector</a>
1179+
* WebSocket connection. to send audio from a Vonage Video API session to a WebSocket.
1180+
*
1181+
* @param string $sessionId The session ID.
1182+
*
1183+
* @param string $token The OpenTok token to be used for the Audio Connector to the
1184+
* OpenTok session. You can add token data to identify that the connection
1185+
* is the Audio Connector endpoint or for other identifying data.
1186+
*
1187+
* @param array $websocketOptions Configuration for the Websocket. Contains the following keys:
1188+
* <ul>
1189+
* <li><code>'uri'</code> (string) &mdash; A publically reachable WebSocket URI controlled by the customer for the destination of the connect call. (f.e. wss://service.com/wsendpoint)</li>
1190+
* <li><code>'streams'</code> (array) &mdash; (Optional) The stream IDs of the participants' whose audio is going to be connected. If not provided, all streams in session will be selected.</li>
1191+
* <li><code>'headers'</code> (array) &mdash; (Optional) An object of key/val pairs with additional properties to send to your Websocket server, with a maximum length of 512 bytes.</li>
1192+
* </ul>
1193+
*
1194+
* @return array $response Response from the API, structured as follows:
1195+
* <ul>
1196+
* <li><code>'id'</code> (string) &mdash; A unique ID identifying the Audio Connector
1197+
* WebSocket.</li>
1198+
* <li><code>'connectionId'</code> (string) &mdash; Opentok client connectionId that has been created. This connection will subscribe and forward the streams defined in the payload to the WebSocket, as any other participant, will produce a connectionCreated event on the session.</li>
1199+
* </ul>
1200+
*
1201+
*
1202+
*/
1203+
public function connectAudio(string $sessionId, string $token, array $websocketOptions)
1204+
{
1205+
Validators::validateSessionId($sessionId);
1206+
Validators::validateWebsocketOptions($websocketOptions);
1207+
1208+
return $this->client->connectAudio($sessionId, $token, $websocketOptions);
1209+
}
1210+
11771211
/** @internal */
11781212
private function signString($string, $secret)
11791213
{

src/OpenTok/Util/Client.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace OpenTok\Util;
44

55
use Exception as GlobalException;
6-
use http\Exception\InvalidArgumentException;
76
use OpenTok\Layout;
87
use Firebase\JWT\JWT;
98
use OpenTok\MediaMode;
@@ -107,7 +106,7 @@ private function createAuthHeader()
107106
'iss' => $this->apiKey,
108107
'iat' => time(), // this is in seconds
109108
'exp' => time() + (5 * 60),
110-
'jti' => uniqid(),
109+
'jti' => uniqid('', true),
111110
);
112111
return JWT::encode($token, $this->apiSecret, 'HS256');
113112
}
@@ -836,11 +835,31 @@ public function forceMuteAll(string $sessionId, array $options)
836835
return $jsonResponse;
837836
}
838837

839-
//echo 'Uh oh! ' . $e->getMessage();
840-
//echo 'HTTP request URL: ' . $e->getRequest()->getUrl() . "\n";
841-
//echo 'HTTP request: ' . $e->getRequest() . "\n";
842-
//echo 'HTTP response status: ' . $e->getResponse()->getStatusCode() . "\n";
843-
//echo 'HTTP response: ' . $e->getResponse() . "\n";
838+
public function connectAudio(string $sessionId, string $token, array $websocketOptions)
839+
{
840+
$request = new Request(
841+
'POST',
842+
'/v2/project/' . $this->apiKey . '/connect'
843+
);
844+
845+
$body = [
846+
'sessionId' => $sessionId,
847+
'token' => $token,
848+
'websocket' => $websocketOptions
849+
];
850+
851+
try {
852+
$response = $this->client->send($request, [
853+
'debug' => $this->isDebug(),
854+
'json' => $body
855+
]);
856+
$jsonResponse = json_decode($response->getBody(), true);
857+
} catch (\Exception $e) {
858+
$this->handleException($e);
859+
return false;
860+
}
861+
return $jsonResponse;
862+
}
844863

845864
private function handleException($e)
846865
{

src/OpenTok/Util/Validators.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public static function validateApiSecret($apiSecret)
6161
throw new InvalidArgumentException('The apiSecret was not a string: ' . print_r($apiSecret, true));
6262
}
6363
}
64+
6465
public static function validateApiUrl($apiUrl)
6566
{
6667
if (!(is_string($apiUrl) && filter_var($apiUrl, FILTER_VALIDATE_URL))) {
@@ -69,6 +70,7 @@ public static function validateApiUrl($apiUrl)
6970
);
7071
}
7172
}
73+
7274
public static function validateClient($client)
7375
{
7476
if (isset($client) && !($client instanceof Client)) {
@@ -371,7 +373,7 @@ public static function validateResolution($resolution)
371373

372374
public static function validateStreamId($streamId)
373375
{
374-
if (!(is_string($streamId))) {
376+
if (!(is_string($streamId)) || empty($streamId)) {
375377
throw new InvalidArgumentException('The streamId was not a string: ' . print_r($streamId, true));
376378
}
377379
}
@@ -385,6 +387,13 @@ public static function validateLayoutClassList($layoutClassList, $format = 'JSON
385387
}
386388
}
387389

390+
public static function validateWebsocketOptions(array $websocketOptions)
391+
{
392+
if (!array_key_exists('uri', $websocketOptions)) {
393+
throw new InvalidArgumentException('Websocket configuration must have a uri');
394+
}
395+
}
396+
388397
public static function validateLayoutClassListItem($layoutClassList)
389398
{
390399
if (!is_string($layoutClassList['id'])) {

tests/OpenTokTest/OpenTokTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,61 @@ public function testForceDisconnectConnectionException()
13411341
$this->opentok->forceDisconnect($sessionId, $connectionId);
13421342
}
13431343

1344+
public function testCannotConnectAudioStreamWithInvalidSessionId()
1345+
{
1346+
$this->setupOTWithMocks([[
1347+
'code' => 200
1348+
]]);
1349+
1350+
$this->expectException(InvalidArgumentException::class);
1351+
$this->expectErrorMessage('Null or empty session ID is not valid: ');
1352+
$this->opentok->connectAudio('', '2398523', []);
1353+
}
1354+
1355+
public function testCannotConnectAudioStreamWithoutWebsocketUri()
1356+
{
1357+
$this->expectException(InvalidArgumentException::class);
1358+
$this->expectErrorMessage('Websocket configuration must have a uri');
1359+
$this->setupOTWithMocks([[
1360+
'code' => 200
1361+
]]);
1362+
1363+
$badPayload = [
1364+
'streams' => ['333425', 'asfasrst'],
1365+
'headers' => ['key' => 'value']
1366+
];
1367+
1368+
$this->opentok->connectAudio('9999', 'wrwetg', $badPayload);
1369+
}
1370+
1371+
public function testCanConnectAudioStreamWithWebsocket()
1372+
{
1373+
$this->setupOTWithMocks([[
1374+
'code' => 200,
1375+
'headers' => [
1376+
'Content-Type' => 'application/json'
1377+
],
1378+
'path' => '/v2/project/APIKEY/connect'
1379+
]]);
1380+
1381+
$sessionId = '1_MX4xMjM0NTY3OH4-VGh1IEZlYiAyNyAwNDozODozMSBQU1QgMjAxNH4wLjI0NDgyMjI';
1382+
$token = '063e72a4-64b4-43c8-9da5-eca071daab89';
1383+
$websocketConfig = [
1384+
'uri' => 'ws://service.com/wsendpoint',
1385+
'streams' => [
1386+
'we9r885',
1387+
'9238fujs'
1388+
],
1389+
'headers' => [
1390+
'key1' => 'value'
1391+
]
1392+
];
1393+
1394+
$response = $this->opentok->connectAudio($sessionId, $token, $websocketConfig);
1395+
$this->assertEquals('063e72a4-64b4-43c8-9da5-eca071daab89', $response['id']);
1396+
$this->assertEquals('7aebb3a4-3d86-4962-b317-afb73e05439d', $response['connectionId']);
1397+
}
1398+
13441399
public function testCanStartBroadcastWithRmtp()
13451400
{
13461401
$this->setupOTWithMocks([[

tests/OpenTokTest/Util/ClientTest.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,35 @@
33
namespace OpenTokTest\Util;
44

55
use GuzzleHttp\Client as GuzzleHttpClient;
6-
use GuzzleHttp\Exception\RequestException;
76
use GuzzleHttp\Handler\MockHandler;
87
use GuzzleHttp\HandlerStack;
9-
use GuzzleHttp\Psr7\Request;
108
use GuzzleHttp\Psr7\Response;
119
use OpenTok\Exception\SignalAuthenticationException;
1210
use OpenTok\Exception\SignalConnectionException;
13-
use OpenTok\Exception\SignalNetworkConnectionException;
1411
use OpenTok\Exception\SignalUnexpectedValueException;
1512
use OpenTok\Util\Client;
1613
use PHPUnit\Framework\TestCase;
17-
use Prophecy\Argument;
18-
use Psr\Http\Message\RequestInterface;
1914

2015
class ClientTest extends TestCase
2116
{
22-
public function testHandlesSignalErrorHandlesNoResponse()
17+
public function testCanAddAudioStreamToWebsocket()
2318
{
24-
$this->expectException(SignalNetworkConnectionException::class);
25-
$this->expectExceptionMessage('Unable to communicate with host');
26-
2719
$mock = new MockHandler([
28-
new RequestException('Unable to communicate with host', new Request('GET', 'signals')),
20+
$this->getResponse('connect')
2921
]);
3022
$handlerStack = HandlerStack::create($mock);
3123
$guzzle = new GuzzleHttpClient(['handler' => $handlerStack]);
3224

3325
$client = new Client();
3426
$client->configure('asdf', 'asdf', 'http://localhost/', ['client' => $guzzle]);
35-
$client->signal('sessionabcd', ['type' => 'foo', 'data' => 'bar'], 'connection1234');
27+
28+
$websocketDummy = [
29+
'uri' => 'ws://test'
30+
];
31+
32+
$response = $client->connectAudio('ddd', 'sarar55r', $websocketDummy);
33+
$this->assertEquals('063e72a4-64b4-43c8-9da5-eca071daab89', $response['id']);
34+
$this->assertEquals('7aebb3a4-3d86-4962-b317-afb73e05439d', $response['connectionId']);
3635
}
3736

3837
public function testHandlesSignalErrorHandles400Response()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": "063e72a4-64b4-43c8-9da5-eca071daab89",
3+
"connectionId": "7aebb3a4-3d86-4962-b317-afb73e05439d"
4+
}

tests/OpenTokTest/Validators/ValidatorsTest.php

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,56 @@
88

99
class ValidatorsTest extends TestCase
1010
{
11-
public function testWillPassCorrectPayload(): void
11+
public function testWillValidateStringApiKey(): void
12+
{
13+
$this->expectNotToPerformAssertions();
14+
$apiKey = '47347801';
15+
Validators::validateApiKey($apiKey);
16+
}
17+
18+
public function testWillValidateIntegerApiKey(): void
19+
{
20+
$this->expectNotToPerformAssertions();
21+
$apiKey = 47347801;
22+
Validators::validateApiKey($apiKey);
23+
}
24+
25+
public function testWillInvalidateApiKey(): void
26+
{
27+
$this->expectException(InvalidArgumentException::class);
28+
$apiKey = [47347801];
29+
Validators::validateApiKey($apiKey);
30+
}
31+
32+
public function testWillValidateApiSecret(): void
33+
{
34+
$this->expectNotToPerformAssertions();
35+
$secret = 'cdff574f0b071230be098e279d16931116c43fcf';
36+
Validators::validateApiSecret($secret);
37+
}
38+
39+
public function testWillInvalidateApiSecret(): void
40+
{
41+
$this->expectException(InvalidArgumentException::class);
42+
$secret = 3252556;
43+
Validators::validateApiSecret($secret);
44+
}
45+
46+
public function testWillValidateApiUrl(): void
47+
{
48+
$this->expectNotToPerformAssertions();
49+
$apiUrl = 'https://api.opentok.com';
50+
Validators::validateApiUrl($apiUrl);
51+
}
52+
53+
public function testWillInvalidateApiUrl(): void
54+
{
55+
$this->expectException(InvalidArgumentException::class);
56+
$apiUrl = '[email protected]';
57+
Validators::validateApiUrl($apiUrl);
58+
}
59+
60+
public function testWillPassCorrectForceMutePayload(): void
1261
{
1362
$this->expectNotToPerformAssertions();
1463

@@ -89,6 +138,31 @@ public function testWillFailWhenStreamIdsIsNotArray(): void
89138
Validators::validateForceMuteAllOptions($options);
90139
}
91140

141+
public function testWillValidateWebsocketConfiguration(): void
142+
{
143+
$this->expectNotToPerformAssertions();
144+
$websocketConfig = [
145+
'uri' => 'ws://valid-websocket',
146+
'streams' => [
147+
'525503c7-913e-43a1-84b4-31b2e9fe668b',
148+
'14026813-4f50-4a5a-9b72-fea25430916d'
149+
]
150+
];
151+
Validators::validateWebsocketOptions($websocketConfig);
152+
}
153+
154+
public function testWillThrowExceptionOnInvalidWebsocketConfiguration(): void
155+
{
156+
$this->expectException(InvalidArgumentException::class);
157+
158+
$websocketConfig = [
159+
'streams' => [
160+
'525503c7-913e-43a1-84b4-31b2e9fe668b',
161+
'14026813-4f50-4a5a-9b72-fea25430916d'
162+
]
163+
];
164+
Validators::validateWebsocketOptions($websocketConfig);
165+
}
92166

93167
/**
94168
* @dataProvider resolutionProvider
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": "063e72a4-64b4-43c8-9da5-eca071daab89",
3+
"connectionId": "7aebb3a4-3d86-4962-b317-afb73e05439d"
4+
}

0 commit comments

Comments
 (0)