diff --git a/src/OpenTok/BroadcastList.php b/src/OpenTok/BroadcastList.php new file mode 100644 index 00000000..109dd5bf --- /dev/null +++ b/src/OpenTok/BroadcastList.php @@ -0,0 +1,79 @@ + null, + 'apiSecret' => null, + 'apiUrl' => 'https://api.opentok.com', + 'client' => null + ); + $options = array_merge($defaults, array_intersect_key($options, $defaults)); + list($apiKey, $apiSecret, $apiUrl, $client) = array_values($options); + + // validate params + Validators::validateBroadcastListData($broadcastListData); + Validators::validateClient($client); + + $this->data = $broadcastListData; + + $this->client = isset($client) ? $client : new Client(); + if (!$this->client->isConfigured()) { + Validators::validateApiKey($apiKey); + Validators::validateApiSecret($apiSecret); + Validators::validateApiUrl($apiUrl); + + $this->client->configure($apiKey, $apiSecret, $apiUrl); + } + } + + /** + * Returns the number of total archives for the API key. + */ + public function totalCount() + { + return $this->data['count']; + } + + /** + * Returns an array of Archive objects. + */ + public function getItems() + { + if (!$this->items) { + $items = array(); + foreach ($this->data['items'] as $broadcastData) { + $items[] = new Broadcast($broadcastData, array( 'client' => $this->client )); + } + $this->items = $items; + } + return $this->items; + } +} diff --git a/src/OpenTok/OpenTok.php b/src/OpenTok/OpenTok.php index c9b13c1b..bd990cb8 100755 --- a/src/OpenTok/OpenTok.php +++ b/src/OpenTok/OpenTok.php @@ -4,6 +4,7 @@ use OpenTok\Layout; use OpenTok\Util\Client; +use OpenTok\BroadcastList; use OpenTok\Util\Validators; use OpenTok\Exception\InvalidArgumentException; use OpenTok\Exception\UnexpectedValueException; @@ -478,6 +479,33 @@ public function forceDisconnect($sessionId, $connectionId) return $this->client->forceDisconnect($sessionId, $connectionId); } + /** + * Returns an BroadcastList. The items() method of this object returns a list of + * broadcasts that are completed and in-progress, for your API key. + * + * @param integer $offset Optional. The index offset of the first broadcast. 0 is offset of the + * most recently started broadcast. 1 is the offset of the broadcast that started prior to the most + * recent broadcast. If you do not specify an offset, 0 is used. + * @param integer $count Optional. The number of broadcasts to be returned. The maximum number of + * broadcasts returned is 1000. + * @param string $sessionId Optional. The OpenTok session Id for which you want to retrieve broadcasts for. If no session Id + * is specified, the method will return archives from all sessions created with the API key. + * + * @return BroadcastList An ArchiveList object. Call the items() method of the ArchiveList object + * to return an array of Archive objects. + */ + public function listBroadcasts($offset = 0, $count = null, $sessionId = null) + { + // validate params + Validators::validateOffsetAndCount($offset, $count); + if (!is_null($sessionId)) { + Validators::validateSessionIdBelongsToKey($sessionId, $this->apiKey); + } + + $broadcastListData = $this->client->listBroadcasts($offset, $count, $sessionId); + return new BroadcastList($broadcastListData, array( 'client' => $this->client )); + } + /** * Starts a live streaming broadcast of an OpenTok session. * diff --git a/src/OpenTok/Util/Client.php b/src/OpenTok/Util/Client.php index 12fced91..fa271724 100755 --- a/src/OpenTok/Util/Client.php +++ b/src/OpenTok/Util/Client.php @@ -287,6 +287,32 @@ public function listArchives($offset, $count, $sessionId) return $archiveListJson; } + public function listBroadcasts($offset, $count, $sessionId) + { + $request = new Request('GET', '/v2/project/' . $this->apiKey . '/broadcast'); + $queryParams = []; + if ($offset != 0) { + $queryParams['offset'] = $offset; + } + if (!empty($count)) { + $queryParams['count'] = $count; + } + if (!empty($sessionId)) { + $queryParams['sessionId'] = $sessionId; + } + try { + $response = $this->client->send($request, [ + 'debug' => $this->isDebug(), + 'query' => $queryParams + ]); + $broadcastListJson = json_decode($response->getBody(), true); + } catch (\Exception $e) { + $this->handleException($e); + return; + } + return $broadcastListJson; + } + public function startBroadcast(string $sessionId, array $options): array { $request = new Request( diff --git a/src/OpenTok/Util/Validators.php b/src/OpenTok/Util/Validators.php index fe0eef50..16c82a1b 100755 --- a/src/OpenTok/Util/Validators.php +++ b/src/OpenTok/Util/Validators.php @@ -21,6 +21,7 @@ class Validators public static $guidRegEx = '/^\[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}\$/'; public static $archiveSchemaUri; public static $broadcastSchemaUri; + public static $broadcastListSchemaUri; public static function validateApiKey($apiKey) { @@ -195,6 +196,24 @@ public static function validateArchiveListData($archiveListData) ); } } + + public static function validateBroadcastListData($broadcastListData) + { + if (!self::$broadcastListSchemaUri) { + self::$broadcastListSchemaUri = __DIR__ . '/broadcast-list-schema.json'; + } + $document = new Document(); + // have to do a encode+decode so that json objects decoded as arrays from Guzzle + // are re-encoded as objects instead + $document->loadData(json_decode(json_encode($broadcastListData))); + $document->loadSchema(self::$broadcastListSchemaUri); + if (!$document->validate()) { + throw new InvalidArgumentException( + 'The broadcast data provided is not valid. Errors:' . $document->lastError . ' broadcastListData:' . print_r($broadcastListData, true) + ); + } + } + public static function validateOffsetAndCount($offset, $count) { if ( diff --git a/src/OpenTok/Util/broadcast-list-schema.json b/src/OpenTok/Util/broadcast-list-schema.json new file mode 100644 index 00000000..711b2e41 --- /dev/null +++ b/src/OpenTok/Util/broadcast-list-schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type":"object", + "properties": { + "count": { + "description": "Total number of broadcasts of the query", + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "title": "Broadcast", + "description": "An OpenTok Broadcast", + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$" + }, + "sessionId": { + "type": "string" + }, + "projectId": { + "type": "integer" + }, + "createdAt": { + "type" : "integer" + }, + "updatedAt": { + "type" : "integer" + }, + "resolution": { + "type": "string" + }, + "broadcastUrls": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "hls": { + "type": "string" + } + } + } + ] + } + }, + "required": ["id", "sessionId", "projectId", "createdAt", "updatedAt"] + } + } + }, + "required": ["count", "items"] + } \ No newline at end of file diff --git a/src/OpenTok/Util/broadcast-schema.json b/src/OpenTok/Util/broadcast-schema.json index e019971c..6bc20046 100644 --- a/src/OpenTok/Util/broadcast-schema.json +++ b/src/OpenTok/Util/broadcast-schema.json @@ -11,7 +11,7 @@ "sessionId": { "type": "string" }, - "partnerId": { + "projectId": { "type": "integer" }, "createdAt": { @@ -20,6 +20,9 @@ "updatedAt": { "type" : "integer" }, + "resolution": { + "type": "string" + }, "broadcastUrls": { "oneOf": [ { @@ -36,5 +39,5 @@ ] } }, - "required": ["id", "sessionId", "partnerId", "createdAt", "updatedAt"] + "required": ["id", "sessionId", "projectId", "createdAt", "updatedAt"] } diff --git a/tests/OpenTokTest/OpenTokTest.php b/tests/OpenTokTest/OpenTokTest.php index d17a8f52..7d48d0d2 100644 --- a/tests/OpenTokTest/OpenTokTest.php +++ b/tests/OpenTokTest/OpenTokTest.php @@ -18,6 +18,7 @@ use PHPUnit\Framework\TestCase; use GuzzleHttp\Handler\MockHandler; use InvalidArgumentException as GlobalInvalidArgumentException; +use OpenTok\BroadcastList; use OpenTok\Exception\AuthenticationException; use OpenTok\Exception\DomainException as ExceptionDomainException; use OpenTok\Exception\InvalidArgumentException; @@ -1161,6 +1162,119 @@ public function testForceDisconnectConnectionException() } + public function testListsBroadcasts() + { + // Arrange + $this->setupOTWithMocks([[ + 'code' => 200, + 'headers' => [ + 'Content-Type' => 'application/json' + ], + 'path' => 'v2/project/APIKEY/broadcast/index' + ]]); + + // Act + $broadcastList = $this->opentok->listBroadcasts(); + + // Assert + $this->assertCount(1, $this->historyContainer); + + $request = $this->historyContainer[0]['request']; + $this->assertEquals('GET', strtoupper($request->getMethod())); + $this->assertEquals('/v2/project/'.$this->API_KEY.'/broadcast', $request->getUri()->getPath()); + $this->assertEquals('api.opentok.com', $request->getUri()->getHost()); + $this->assertEquals('https', $request->getUri()->getScheme()); + + $authString = $request->getHeaderLine('X-OPENTOK-AUTH'); + $this->assertEquals(true, TestHelpers::validateOpenTokAuthHeader($this->API_KEY, $this->API_SECRET, $authString)); + + $userAgent = $request->getHeaderLine('User-Agent'); + $this->assertNotEmpty($userAgent); + $this->assertStringStartsWith('OpenTok-PHP-SDK/4.9.0', $userAgent); + + $this->assertInstanceOf(BroadcastList::class, $broadcastList); + } + + public function testListsBroadcastsWithOffsetAndCount() + { + // Arrange + $this->setupOTWithMocks([[ + 'code' => 200, + 'headers' => [ + 'Content-Type' => 'application/json' + ], + 'path' => 'v2/project/APIKEY/broadcast/index-page-2' + ]]); + + // Act + $broadcastList = $this->opentok->listBroadcasts(1, 1); + + // Assert + $this->assertCount(1, $this->historyContainer); + + $request = $this->historyContainer[0]['request']; + $this->assertEquals('GET', strtoupper($request->getMethod())); + $this->assertEquals('/v2/project/'.$this->API_KEY.'/broadcast', $request->getUri()->getPath()); + $this->assertEquals('api.opentok.com', $request->getUri()->getHost()); + $this->assertEquals('https', $request->getUri()->getScheme()); + + $authString = $request->getHeaderLine('X-OPENTOK-AUTH'); + $this->assertEquals(true, TestHelpers::validateOpenTokAuthHeader($this->API_KEY, $this->API_SECRET, $authString)); + + // TODO: test the dynamically built User Agent string + $userAgent = $request->getHeaderLine('User-Agent'); + $this->assertNotEmpty($userAgent); + $this->assertStringStartsWith('OpenTok-PHP-SDK/4.9.0', $userAgent); + + $this->assertInstanceOf(BroadcastList::class, $broadcastList); + $this->assertEquals(1, $broadcastList->totalCount()); + $this->assertEquals('1c46ad10-0a81-464c-9759-748b707d3734', $broadcastList->getItems()[0]->id); + } + + public function testListsBroadcastsWithSessionId() + { + // Arrange + $this->setupOTWithMocks([[ + 'code' => 200, + 'headers' => [ + 'Content-Type' => 'application/json' + ], + 'path' => 'v2/project/APIKEY/broadcast/index' + ]]); + + $sessionId = '1_MX4xMjM0NTY3OH4-VGh1IEZlYiAyNyAwNDozODozMSBQU1QgMjAxNH4wLjI0NDgyMjI'; + $bogusApiKey = '12345678'; + $bogusApiSecret = '0123456789abcdef0123456789abcdef0123456789'; + $opentok = new OpenTok($bogusApiKey, $bogusApiSecret); + + // Act + $broadcastList = $this->opentok->listBroadcasts(0, null, $sessionId); + + // Assert + $this->assertCount(1, $this->historyContainer); + + $request = $this->historyContainer[0]['request']; + $this->assertEquals('GET', strtoupper($request->getMethod())); + $this->assertEquals('/v2/project/'.$this->API_KEY.'/broadcast', $request->getUri()->getPath()); + $this->assertEquals('api.opentok.com', $request->getUri()->getHost()); + $this->assertEquals('https', $request->getUri()->getScheme()); + + $authString = $request->getHeaderLine('X-OPENTOK-AUTH'); + $this->assertEquals(true, TestHelpers::validateOpenTokAuthHeader($this->API_KEY, $this->API_SECRET, $authString)); + + // TODO: test the dynamically built User Agent string + $userAgent = $request->getHeaderLine('User-Agent'); + $this->assertNotEmpty($userAgent); + $this->assertStringStartsWith('OpenTok-PHP-SDK/4.9.0', $userAgent); + + $this->assertInstanceOf(BroadcastList::class, $broadcastList); + $this->assertEquals(2, $broadcastList->totalCount()); + $this->assertEquals($sessionId, $broadcastList->getItems()[0]->sessionId); + $this->assertEquals($sessionId, $broadcastList->getItems()[1]->sessionId); + $this->assertEquals('1748b707-0a81-464c-9759-c46ad10d3734', $broadcastList->getItems()[0]->id); + $this->assertEquals('1c46ad10-0a81-464c-9759-748b707d3734', $broadcastList->getItems()[1]->id); + } + public function testStartsBroadcast() { // Arrange diff --git a/tests/mock/v2/project/APIKEY/broadcast/index b/tests/mock/v2/project/APIKEY/broadcast/index new file mode 100644 index 00000000..cca52b03 --- /dev/null +++ b/tests/mock/v2/project/APIKEY/broadcast/index @@ -0,0 +1,47 @@ +{ + "count" : 2, + "items" : [ + { + "id": "1748b707-0a81-464c-9759-c46ad10d3734", + "sessionId": "1_MX4xMjM0NTY3OH4-VGh1IEZlYiAyNyAwNDozODozMSBQU1QgMjAxNH4wLjI0NDgyMjI", + "projectId": 100, + "createdAt": 1437676551000, + "updatedAt": 1437676551000, + "resolution": "640x480", + "broadcastUrls": { + "hls" : "http://server/fakepath/playlist.m3u8", + "rtmp": { + "foo": { + "serverUrl": "rtmps://myfooserver/myfooapp", + "streamName": "myfoostream", + "status": "started" + }, + "bar": { + "serverUrl": "rtmp://mybarserver/mybarapp", + "streamName": "mybarstream", + "status": "live" + } + } + }, + "status": "started" + }, { + "id": "1c46ad10-0a81-464c-9759-748b707d3734", + "sessionId": "1_MX4xMjM0NTY3OH4-VGh1IEZlYiAyNyAwNDozODozMSBQU1QgMjAxNH4wLjI0NDgyMjI", + "projectId": 100, + "createdAt": 1437676853000, + "updatedAt": 1437676853000, + "resolution": "640x480", + "broadcastUrls": { + "hls" : "http://server/fakepath2/playlist.m3u8", + "rtmp": { + "foo": { + "serverUrl": "rtmp://myfooserver/myfooapps", + "streamName": "myfoostreams", + "status": "live" + } + } + }, + "status": "live" + } + ] +} diff --git a/tests/mock/v2/project/APIKEY/broadcast/index-page-2 b/tests/mock/v2/project/APIKEY/broadcast/index-page-2 new file mode 100644 index 00000000..8237d4c8 --- /dev/null +++ b/tests/mock/v2/project/APIKEY/broadcast/index-page-2 @@ -0,0 +1,24 @@ +{ + "count" : 1, + "items" : [ + { + "id": "1c46ad10-0a81-464c-9759-748b707d3734", + "sessionId": "1_MX4xMjM0NTY3OH4-VGh1IEZlYiAyNyAwNDozODozMSBQU1QgMjAxNH4wLjI0NDgyMjI", + "projectId": 100, + "createdAt": 1437676853000, + "updatedAt": 1437676853000, + "resolution": "640x480", + "broadcastUrls": { + "hls" : "http://server/fakepath2/playlist.m3u8", + "rtmp": { + "foo": { + "serverUrl": "rtmp://myfooserver/myfooapps", + "streamName": "myfoostreams", + "status": "live" + } + } + }, + "status": "live" + } + ] +}