Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/OpenTok/Archive.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@
* "available"; for other archives, (including archives with the status "uploaded") this property is
* set to null. The download URL is obfuscated, and the file is only available from the URL for
* 10 minutes. To generate a new URL, call the Archive.listArchives() or OpenTok.getArchive() method.
*
* @property bool $hasTranscription
* Whether the archive has a transcription of the audio of the session (true) or not (false).
*
* @property array $transcription
* Properties of the transcription attached to this archive, including status, url, reason,
* primaryLanguageCode, and hasSummary.
*/
class Archive
{
Expand Down Expand Up @@ -184,6 +191,8 @@ public function __get($name)
case 'streamMode':
case 'maxBitrate':
case 'quantizationParameter':
case 'hasTranscription':
case 'transcription':
return $this->data[$name];
case 'multiArchiveTag':
return $this->multiArchiveTag;
Expand Down
36 changes: 36 additions & 0 deletions src/OpenTok/OpenTok.php
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,19 @@ public function getRender($renderId): Render
* (HD portrait), or "1080x1920" (FHD portrait). This property only applies to composed archives. If you set
* this property and set the outputMode property to "individual", a call to the method
* results in an error.</li>
*
* <li><code>'hasTranscription'</code> (Boolean) &mdash; Whether the archive will have a transcription of the audio
* of the session (true) or not (false, the default).</li>
*
* <li><code>'transcriptionProperties'</code> (Array) &mdash; An array defining transcription properties. This array
* includes the following keys:
* <ul>
* <li><code>'primaryLanguageCode'</code> (String) &mdash; The primary language spoken in the archive to be
* transcribed, in BCP-47 format (e.g., "en-US", "es-ES", "pt-BR").</li>
* <li><code>'hasSummary'</code> (Boolean) &mdash; Whether the transcription should include a summary of the
* session (true) or not (false, the default).</li>
* </ul>
* </li>
* </ul>
*
* @return Archive The Archive object, which includes properties defining the archive, including
Expand All @@ -570,16 +583,37 @@ public function startArchive(string $sessionId, $options = []): Archive
'outputMode' => OutputMode::COMPOSED,
'resolution' => null,
'streamMode' => StreamMode::AUTO,
'hasTranscription' => false,
'transcriptionProperties' => null,
);

// Horrible hack to workaround the defaults behaviour
if (isset($options['maxBitrate'])) {
$maxBitrate = $options['maxBitrate'];
}

// Preserve transcription fields from user input
$hasTranscription = isset($options['hasTranscription']) ? $options['hasTranscription'] : false;
$transcriptionProperties = isset($options['transcriptionProperties']) ? $options['transcriptionProperties'] : null;

$options = array_merge($defaults, array_intersect_key($options, $defaults));
list($name, $hasVideo, $hasAudio, $outputMode, $resolution, $streamMode) = array_values($options);

// Re-add transcription options to options array for API call
$options['hasTranscription'] = $hasTranscription;
if ($hasTranscription && $transcriptionProperties !== null) {
$options['transcriptionProperties'] = $transcriptionProperties;
}

if (isset($maxBitrate)) {
$options['maxBitrate'] = $maxBitrate;
}

// Remove null values before sending to API
$options = array_filter($options, function($value) {
return $value !== null;
});

if (isset($maxBitrate)) {
$options['maxBitrate'] = $maxBitrate;
}
Expand All @@ -595,6 +629,8 @@ public function startArchive(string $sessionId, $options = []): Archive
Validators::validateArchiveHasAudio($hasAudio);
Validators::validateArchiveOutputMode($outputMode);
Validators::validateHasStreamMode($streamMode);
Validators::validateArchiveHasTranscription($hasTranscription);
Validators::validateArchiveTranscriptionProperties($transcriptionProperties);

if ((is_null($resolution) || empty($resolution)) && $outputMode === OutputMode::COMPOSED) {
$options['resolution'] = "640x480";
Expand Down
39 changes: 39 additions & 0 deletions src/OpenTok/Util/Validators.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,4 +525,43 @@ public static function validateBroadcastBitrate($maxBitRate): void
throw new \OutOfBoundsException('Max Bitrate must be between 400000 and 2000000');
}
}

public static function validateArchiveHasTranscription($hasTranscription)
{
if (!is_bool($hasTranscription)) {
throw new InvalidArgumentException(
'hasTranscription must be either true or false.'
);
}
}

public static function validateArchiveTranscriptionProperties($transcriptionProperties)
{
if ($transcriptionProperties === null) {
return;
}

if (!is_array($transcriptionProperties)) {
throw new InvalidArgumentException(
'transcriptionProperties must be an array.'
);
}

if (isset($transcriptionProperties['primaryLanguageCode'])) {
if (!is_string($transcriptionProperties['primaryLanguageCode']) ||
empty($transcriptionProperties['primaryLanguageCode'])) {
throw new InvalidArgumentException(
'primaryLanguageCode must be a non-empty string in BCP-47 format.'
);
}
}

if (isset($transcriptionProperties['hasSummary'])) {
if (!is_bool($transcriptionProperties['hasSummary'])) {
throw new InvalidArgumentException(
'hasSummary must be either true or false.'
);
}
}
}
}
201 changes: 201 additions & 0 deletions tests/OpenTokTest/OpenTokTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,207 @@ public function testGetsExpiredArchive(): void
$this->assertEquals("expired", $archive->status);
}

public function testStartsArchiveWithTranscription(): void
{
// Arrange
$this->setupOTWithMocks([[
'code' => 200,
'headers' => [
'Content-Type' => 'application/json'
],
'path' => 'v2/project/APIKEY/archive/session_hasTranscription-true'
]]);

// This sessionId was generated using a different apiKey, but this method doesn't do any
// decoding to check, so it's fine.
$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act
$archive = $this->opentok->startArchive($sessionId, [
'hasTranscription' => true,
'transcriptionProperties' => [
'primaryLanguageCode' => 'en-US',
'hasSummary' => false
]
]);

// Assert
$this->assertCount(1, $this->historyContainer);

$request = $this->historyContainer[0]['request'];
$this->assertEquals('POST', strtoupper($request->getMethod()));
$this->assertEquals('/v2/project/' . $this->API_KEY . '/archive', $request->getUri()->getPath());
$this->assertEquals('api.opentok.com', $request->getUri()->getHost());
$this->assertEquals('https', $request->getUri()->getScheme());

$contentType = $request->getHeaderLine('Content-Type');
$this->assertNotEmpty($contentType);
$this->assertEquals('application/json', $contentType);

$authString = $request->getHeaderLine('X-OPENTOK-AUTH');
$this->assertEquals(true, TestHelpers::validateOpenTokAuthHeader($this->API_KEY, $this->API_SECRET, $authString));

// Test request body contains transcription fields
$body = json_decode($request->getBody());
$this->assertEquals(true, $body->hasTranscription);
$this->assertEquals('en-US', $body->transcriptionProperties->primaryLanguageCode);
$this->assertEquals(false, $body->transcriptionProperties->hasSummary);

// Test response properties
$this->assertInstanceOf('OpenTok\Archive', $archive);
$this->assertEquals(true, $archive->hasTranscription);
$this->assertIsArray($archive->transcription);
$this->assertEquals('requested', $archive->transcription['status']);
$this->assertEquals('en-US', $archive->transcription['primaryLanguageCode']);
$this->assertEquals(false, $archive->transcription['hasSummary']);
}

public function testStartsArchiveWithTranscriptionAndSummary(): void
{
// Arrange
$this->setupOTWithMocks([[
'code' => 200,
'headers' => [
'Content-Type' => 'application/json'
],
'path' => 'v2/project/APIKEY/archive/session_transcription-with-summary'
]]);

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act
$archive = $this->opentok->startArchive($sessionId, [
'hasTranscription' => true,
'transcriptionProperties' => [
'primaryLanguageCode' => 'es-ES',
'hasSummary' => true
]
]);

// Assert
$this->assertCount(1, $this->historyContainer);

$request = $this->historyContainer[0]['request'];

// Test request body contains transcription fields
$body = json_decode($request->getBody());
$this->assertEquals(true, $body->hasTranscription);
$this->assertEquals('es-ES', $body->transcriptionProperties->primaryLanguageCode);
$this->assertEquals(true, $body->transcriptionProperties->hasSummary);

// Test response properties
$this->assertInstanceOf('OpenTok\Archive', $archive);
$this->assertEquals(true, $archive->hasTranscription);
$this->assertIsArray($archive->transcription);
$this->assertEquals('requested', $archive->transcription['status']);
$this->assertEquals('es-ES', $archive->transcription['primaryLanguageCode']);
$this->assertEquals(true, $archive->transcription['hasSummary']);
}

public function testStartsArchiveWithoutTranscription(): void
{
// Arrange
$this->setupOTWithMocks([[
'code' => 200,
'headers' => [
'Content-Type' => 'application/json'
],
'path' => 'v2/project/APIKEY/archive/session'
]]);

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act - explicitly set hasTranscription to false
$archive = $this->opentok->startArchive($sessionId, [
'hasTranscription' => false
]);

// Assert
$this->assertCount(1, $this->historyContainer);

$request = $this->historyContainer[0]['request'];

// Test request body
$body = json_decode($request->getBody());
$this->assertEquals(false, $body->hasTranscription);
$this->assertObjectNotHasAttribute('transcriptionProperties', $body);

// Test response properties (archive without transcription)
$this->assertInstanceOf('OpenTok\Archive', $archive);
// The hasTranscription property should not be set in response if transcription is disabled
}

public function testCannotStartArchiveWithInvalidTranscriptionProperties(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('transcriptionProperties must be an array.');

// Set up the OpenTok instance first
$this->setupOTWithMocks([]);

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act - pass invalid transcriptionProperties (not an array)
$this->opentok->startArchive($sessionId, [
'hasTranscription' => true,
'transcriptionProperties' => 'invalid'
]);
}

public function testCannotStartArchiveWithInvalidHasTranscription(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('hasTranscription must be either true or false.');

// Set up the OpenTok instance first
$this->setupOTWithMocks([]);

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act - pass invalid hasTranscription (not a boolean)
$this->opentok->startArchive($sessionId, [
'hasTranscription' => 'invalid'
]);
}

public function testCannotStartArchiveWithInvalidPrimaryLanguageCode(): void
{
$this->setupOT();

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('primaryLanguageCode must be a non-empty string in BCP-47 format.');

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act - pass empty primaryLanguageCode
$this->opentok->startArchive($sessionId, [
'hasTranscription' => true,
'transcriptionProperties' => [
'primaryLanguageCode' => '',
'hasSummary' => false
]
]);
}

public function testCannotStartArchiveWithInvalidHasSummary(): void
{
$this->setupOT();

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('hasSummary must be either true or false.');

$sessionId = '2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-';

// Act - pass invalid hasSummary (not a boolean)
$this->opentok->startArchive($sessionId, [
'hasTranscription' => true,
'transcriptionProperties' => [
'primaryLanguageCode' => 'en-US',
'hasSummary' => 'invalid'
]
]);
}

public function testForceDisconnect(): void
{
// Arrange
Expand Down
24 changes: 24 additions & 0 deletions tests/mock/v2/project/APIKEY/archive/session_hasTranscription-true
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"createdAt" : 1394321113584,
"duration" : 0,
"id" : "832641bf-5dbf-41a1-ad94-fea213e59a92",
"name" : null,
"partnerId" : 12345678,
"reason" : "",
"sessionId" : "2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-",
"size" : 0,
"status" : "started",
"url" : null,
"hasVideo" : true,
"hasAudio" : true,
"outputMode" : "composed",
"streamMode" : "auto",
"hasTranscription" : true,
"transcription" : {
"status" : "requested",
"url" : null,
"reason" : null,
"primaryLanguageCode" : "en-US",
"hasSummary" : false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"createdAt" : 1394321113584,
"duration" : 0,
"id" : "832641bf-5dbf-41a1-ad94-fea213e59a93",
"name" : null,
"partnerId" : 12345678,
"reason" : "",
"sessionId" : "2_MX44NTQ1MTF-flR1ZSBOb3YgMTIgMDk6NDA6NTkgUFNUIDIwMTN-MC43NjU0Nzh-",
"size" : 0,
"status" : "started",
"url" : null,
"hasVideo" : true,
"hasAudio" : true,
"outputMode" : "composed",
"streamMode" : "auto",
"hasTranscription" : true,
"transcription" : {
"status" : "requested",
"url" : null,
"reason" : null,
"primaryLanguageCode" : "es-ES",
"hasSummary" : true
}
}
Loading