Skip to content

Commit

Permalink
feat: Publisher-only role & migration guide update (#524)
Browse files Browse the repository at this point in the history
* feat: Add publisher-only role

* docs: Update changelog & migration guide

* fix: connectionId in ConnectResponse

* refactor: Rename StartCaptions objects
  • Loading branch information
SMadani authored Apr 12, 2024
1 parent 7cbb4f6 commit a306e6d
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 49 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

# [8.5.0] - 2024-04-2?
# [8.5.0] - 2024-04-12
- Added Live Captions and Audio Connector endpoints to Video API
- Added `publisheronly` role to Video API
- Added Verify v2 "Next Workflow" endpoint
- Updated validation logic for Verify v2 `brand` and SMS `from` parameters
- Removed `com.vonage.client.sms.HexUtil`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,19 @@
import java.net.URI;

/**
* Defines values for the <code>properties</code> parameter of the
* {@link VideoClient#startCaptions(StartCaptionsRequest)} method.
* Defines the captioning properties used in {@link VideoClient#startCaptions(CaptionsRequest)}.
*
* @since 8.5.0
*/
public final class StartCaptionsRequest extends AbstractSessionTokenRequest {
public final class CaptionsRequest extends AbstractSessionTokenRequest {
private Language languageCode;
private Integer maxDuration;
private Boolean partialCaptions;
private URI statusCallbackUrl;

private StartCaptionsRequest() {}
private CaptionsRequest() {}

private StartCaptionsRequest(Builder builder) {
private CaptionsRequest(Builder builder) {
super(builder);
statusCallbackUrl = builder.statusCallbackUrl;
languageCode = builder.languageCode;
Expand Down Expand Up @@ -95,7 +94,7 @@ public static Builder builder() {
/**
* Builder for defining the fields in a StartCaptionsRequest object.
*/
public static final class Builder extends AbstractSessionTokenRequest.Builder<StartCaptionsRequest, Builder> {
public static final class Builder extends AbstractSessionTokenRequest.Builder<CaptionsRequest, Builder> {
private URI statusCallbackUrl;
private Language languageCode;
private Integer maxDuration;
Expand Down Expand Up @@ -166,8 +165,8 @@ public Builder partialCaptions(boolean partialCaptions) {
* @return The StartCaptionsRequest object with this builder's settings.
*/
@Override
public StartCaptionsRequest build() {
return new StartCaptionsRequest(this);
public CaptionsRequest build() {
return new CaptionsRequest(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
import java.util.UUID;

/**
* Represents the response from {@link VideoClient#startCaptions(StartCaptionsRequest)}.
* Represents the response from {@link VideoClient#startCaptions(CaptionsRequest)}.
*
* @since 8.5.0
*/
public final class StartCaptionsResponse extends JsonableBaseObject {
public final class CaptionsResponse extends JsonableBaseObject {
private UUID captionsId;

StartCaptionsResponse() {}
CaptionsResponse() {}

/**
* Unique identifier for the audio captioning session.
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/com/vonage/client/video/ConnectResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
import java.util.UUID;

/**
* Represents the response from {@link VideoClient#startCaptions(StartCaptionsRequest)}.
* Represents the response from {@link VideoClient#startCaptions(CaptionsRequest)}.
*
* @since 8.5.0
*/
public final class ConnectResponse extends JsonableBaseObject {
private UUID id, captionsId;
private UUID id, connectionId;

ConnectResponse() {}

Expand All @@ -40,12 +40,12 @@ public UUID getId() {
}

/**
* Unique identifier for the audio captioning session.
* The connection ID for the Audio Connector WebSocket connection in the Vonage Video session.
*
* @return The captions UUID.
* @return The connection UUID.
*/
@JsonProperty("captionsId")
public UUID getCaptionsId() {
return captionsId;
@JsonProperty("connectionId")
public UUID getConnectionId() {
return connectionId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Installation
See [README.md](https://github.com/Vonage/vonage-java-sdk?tab=readme-ov-file#installation) for how to add the SDK as a dependency to your project.
You need to use the `com.vonage:server-sdk:[8.0.0,9.0.0)` co-ordinates from [Maven Central](https://search.maven.org/artifact/com.vonage/server-sdk).
You need to use the `com.vonage:server-sdk:[8.0.0,)` co-ordinates from [Maven Central](https://search.maven.org/artifact/com.vonage/server-sdk).

## Client Initialisation
See [README.md](https://github.com/Vonage/vonage-java-sdk?tab=readme-ov-file#typical-instantiation) for setup instructions.
Expand Down Expand Up @@ -42,7 +42,7 @@ For more detailed usage instructions, see the [Java Sever SDK video guide](https
There are a few small changes to be aware of when migrating to Vonage from OpenTok.
Many of these are straightforward and your IDE will help you with auto-completion, but for clarity, consider the following:

- `sendDTMF` renamed to `playDtmf`
- `playDTMF` renamed to `sendDtmf` for all applicable DTMF endpoints.
- `OpenTok#disableForceMute(String)` replaced by `VideoClient#muteSession(String, boolean, String...)`. You need to set the `active` boolean parameter to `false` to achieve the same effect.
- The `MuteAllProperties` class and parameter in `OpenTok` has been replaced by using the `excludedStreamIds` directly in the method parameter of `VideoClient#muteSession(String, boolean, Collection<String>)` (or `VideoClient#muteSession(String, boolean, String...) for convenience`). These methods replace `OpenTok#forceMuteAll(String, MuteAllProperties)`.
- `ArchiveProperties` and `BroadcastProperties` - as used in request parameters in OpenTok - have been replaced by `Archive` and `Broadcast` respectively. Both use the builder pattern for construction.
Expand All @@ -57,7 +57,14 @@ Many of these are straightforward and your IDE will help you with auto-completio
- `OpenTok#setStreamLayouts(String, StreamListProperties)` replaced by `VideoClient#setStreamLayout(String, List<SessionStream>)` (or `VideoClient#setStreamLayout(String, SessionStream...)` for convenience).
- `OpenTok#signal(String, String, SignalProperties)` and `OpenTok#signal(String, SignalProperties)` replaced by `VideoClient#signal(String, String, SignalRequest)` and `VideoClient#signalAll(String, SignalRequest)`, respectively.
- The structure of tokens obtained used the `generateToken` methods in `OpenTok` and `VideoClient` are different. Vonage uses JWTs, whereas OpenTok uses a custom solution.
- Not all features in `OpenTok` are supported in `VideoClient` yet - see below.
- `OpenTok#startCaptions(String, String, CaptionProperties)` replaced by `VideoClient#startCaptions(CaptionsRequest)`.
- `CaptionProperties` replaced with`CaptionsRequest`.
- `Caption` replaced with `CaptionsResponse`.
- `CaptionsRequest` uses an enum for the `languageCode` instead of a plain string.
- The `token` and `sessionId` are still required and set on the `CaptionsRequest.Builder` object.
- `OpenTok#connectAudioStream(String sessionId, String token, AudioConnectorProperties properties)` replaced by `VideoClient#connectToWebsocket(ConnectRequest request)`
- `AudioConnectorProperties` replaced with `ConnectRequest`
- `AudioConnector` replaced with `ConnectResponse`

## Supported Features
The following is a list of Vonage Video API features and whether the Vonage Java SDK currently supports them:
Expand All @@ -71,8 +78,8 @@ The following is a list of Vonage Video API features and whether the Vonage Java
| Archiving ||
| Live Streaming Broadcasts ||
| SIP Interconnect ||
| Account Management ||
| Experience Composer ||
| Audio Connector ||
| Live Captions ||
| Audio Connector ||
| Live Captions ||
| Account Management ||
| Custom S3/Azure buckets ||
19 changes: 15 additions & 4 deletions src/main/java/com/vonage/client/video/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,40 @@
package com.vonage.client.video;

import com.fasterxml.jackson.annotation.JsonValue;

/**
* Defines values for the role parameter of the {@link TokenOptions.Builder#role(Role role)} method.
*/
public enum Role {

/**
* A subscriber can only subscribe to streams.
*/
SUBSCRIBER,

/**
* A publisher can publish streams, subscribe to streams, and signal. (This is the default value if you do not set a
* role by calling the {@link TokenOptions.Builder#role(Role role)} method.
* A publisher can publish streams, subscribe to streams, and signal. This is the default value
* if you do not set a role by calling the {@link TokenOptions.Builder#role(Role role)} method.
*/
PUBLISHER,

/**
* In addition to the privileges granted to a publisher, a moderator can perform moderation functions, such as
* forcing clients to disconnect, to stop publishing streams, or to mute audio in published streams. See the
* <a href="https://tokbox.com/developer/guides/moderation/">Moderation developer guide</a>.
*/
MODERATOR;
MODERATOR,

/**
* A publisher-only role can publish streams, but not signal.
*
* @since 8.5.0
*/
PUBLISHER_ONLY;

@JsonValue
@Override
public String toString() {
return name().toLowerCase();
return name().toLowerCase().replace("_", "");
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/vonage/client/video/VideoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class VideoClient {
final RestEndpoint<ListStreamCompositionsRequest, ListBroadcastsResponse> listBroadcasts;
final RestEndpoint<String, Broadcast> getBroadcast, stopBroadcast;
final RestEndpoint<Broadcast, Broadcast> createBroadcast;
final RestEndpoint<StartCaptionsRequest, StartCaptionsResponse> startCaptions;
final RestEndpoint<CaptionsRequest, CaptionsResponse> startCaptions;
final RestEndpoint<ConnectRequest, ConnectResponse> connect;

/**
Expand Down Expand Up @@ -707,7 +707,7 @@ public Broadcast stopBroadcast(String broadcastId) {
*
* @since 8.5.0
*/
public StartCaptionsResponse startCaptions(StartCaptionsRequest request) {
public CaptionsResponse startCaptions(CaptionsRequest request) {
return startCaptions.execute(validateRequest(request));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import org.junit.jupiter.api.*;
import java.net.URI;

public class StartCaptionsRequestTest {
public class CaptionsRequestTest {
private final String sessionId = "flR1ZSBPY3QgMjkgMTI6MTM6MjMgUERUIDIwMTN", token = "X.Y.Z";

@Test
Expand All @@ -30,7 +30,7 @@ public void testSerializeAllParams() {
Language language = Language.JA_JP;
String statusCallbackUrl = "https://example.org/video/cb/status";

var request = StartCaptionsRequest.builder()
var request = CaptionsRequest.builder()
.partialCaptions(partialCaptions)
.maxDuration(maxDuration)
.sessionId(sessionId)
Expand All @@ -56,7 +56,7 @@ public void testSerializeAllParams() {

@Test
public void testSerializeRequiredParams() {
var request = StartCaptionsRequest.builder().sessionId(sessionId).token(token).build();
var request = CaptionsRequest.builder().sessionId(sessionId).token(token).build();

String expectedJson = "{\"sessionId\":\""+sessionId+"\",\"token\":\""+token+"\"}";
assertEquals(expectedJson, request.toJson());
Expand All @@ -71,7 +71,7 @@ public void testSerializeRequiredParams() {

@Test
public void testMaxDurationBounds() {
var builder = StartCaptionsRequest.builder().sessionId(sessionId).token(token);
var builder = CaptionsRequest.builder().sessionId(sessionId).token(token);
int min = 300, max = 14400;
assertEquals(min, builder.maxDuration(min).build().getMaxDuration());
assertEquals(max, builder.maxDuration(max).build().getMaxDuration());
Expand All @@ -81,7 +81,7 @@ public void testMaxDurationBounds() {

@Test
public void testStatusCallbackUriLength() {
var builder = StartCaptionsRequest.builder().sessionId(sessionId).token(token);
var builder = CaptionsRequest.builder().sessionId(sessionId).token(token);
String fourteen = "https://t.co/b";
assertEquals(14, fourteen.length());
assertThrows(IllegalArgumentException.class, () -> builder.statusCallbackUrl(fourteen).build());
Expand All @@ -103,14 +103,14 @@ public void testStatusCallbackUriLength() {
@Test
public void testConstructMissingSessionId() {
assertThrows(NullPointerException.class,
() -> StartCaptionsRequest.builder().token(token).build()
() -> CaptionsRequest.builder().token(token).build()
);
}

@Test
public void testConstructMissingToken() {
assertThrows(NullPointerException.class,
() -> StartCaptionsRequest.builder().sessionId(sessionId).build()
() -> CaptionsRequest.builder().sessionId(sessionId).build()
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/test/java/com/vonage/client/video/TokenOptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,16 @@ public void testInvalidParameters() {
assertEquals(1001, data1001.length());
assertThrows(IllegalArgumentException.class, () -> TokenOptions.builder().data(data1001).build());
}

@Test
public void testPublisherOnlyRole() {
TokenOptions options = TokenOptions.builder().role(Role.PUBLISHER_ONLY).build();
assertEquals(Role.PUBLISHER_ONLY, options.getRole());

Jwt jwt = buildJwtWithClaims(options);

Map<String, ?> claims = jwt.getClaims();
assertEquals(2, claims.size());
assertEquals("publisheronly", claims.get("role").toString());
}
}
21 changes: 10 additions & 11 deletions src/test/java/com/vonage/client/video/VideoClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ public void testSendDtmf() throws Exception {

@Test
public void testStartLiveCaptions() throws Exception {
var request = StartCaptionsRequest.builder().token(token).sessionId(sessionId).build();
var request = CaptionsRequest.builder().token(token).sessionId(sessionId).build();
var response = stubResponseAndGet(202,
"{\"captionsId\": \""+captionsId+"\"}",
() -> client.startCaptions(request)
Expand Down Expand Up @@ -736,12 +736,12 @@ public void testStopLiveCaptions() throws Exception {
public void testAudioConnector() throws Exception {
var request = ConnectRequest.builder().token(token).sessionId(sessionId).uri(wssUri).build();
var response = stubResponseAndGet(202,
"{\"id\":\""+connectionId+"\",\"captionsId\": \""+captionsId+"\"}",
"{\"id\":\""+id+"\",\"connectionId\": \""+connectionId+"\"}",
() -> client.connectToWebsocket(request)
);
testJsonableBaseObject(response);
assertEquals(UUID.fromString(connectionId), response.getId());
assertEquals(UUID.fromString(captionsId), response.getCaptionsId());
assertEquals(UUID.fromString(id), response.getId());
assertEquals(UUID.fromString(connectionId), response.getConnectionId());

stubResponseAndAssertThrowsIAX(202, () -> client.startCaptions(null));

Expand Down Expand Up @@ -773,8 +773,7 @@ public void testGenerateToken() {

token = client.generateToken(sessionId,TokenOptions.builder()
.role(Role.SUBSCRIBER)
.expiryLength(Duration
.ofMinutes(12))
.expiryLength(Duration.ofMinutes(12))
.data("foo bar, blah blah")
.initialLayoutClassList(Arrays.asList("c1", "c2", "min", "full"))
.build()
Expand Down Expand Up @@ -1745,11 +1744,11 @@ protected String sampleRequestBodyString() {

@Test
public void testStartLiveCaptionsEndpoint() throws Exception {
new VideoEndpointTestSpec<StartCaptionsRequest, StartCaptionsResponse>() {
new VideoEndpointTestSpec<CaptionsRequest, CaptionsResponse>() {
final String statusCallbackUrl = "https://send-status-to.me";

@Override
protected RestEndpoint<StartCaptionsRequest, StartCaptionsResponse> endpoint() {
protected RestEndpoint<CaptionsRequest, CaptionsResponse> endpoint() {
return client.startCaptions;
}

Expand All @@ -1759,13 +1758,13 @@ protected HttpMethod expectedHttpMethod() {
}

@Override
protected String expectedEndpointUri(StartCaptionsRequest request) {
protected String expectedEndpointUri(CaptionsRequest request) {
return "/v2/project/"+applicationId+"/captions";
}

@Override
protected StartCaptionsRequest sampleRequest() {
return StartCaptionsRequest.builder()
protected CaptionsRequest sampleRequest() {
return CaptionsRequest.builder()
.token(token).partialCaptions(true)
.statusCallbackUrl(statusCallbackUrl)
.sessionId(sessionId).maxDuration(1800)
Expand Down

0 comments on commit a306e6d

Please sign in to comment.