From e0bc90ad2508abf37cbc8195b0397568607efae9 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Tue, 22 Aug 2023 09:56:25 +0100 Subject: [PATCH] Add missing Broadcast & SIP fields (#244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added missing fields to Broadcast * Added SIP stream selection * Bump version: v4.11.0 → v4.12.0 --- .bumpversion.cfg | 2 +- README.md | 4 +- build.gradle | 6 +-- src/main/java/com/opentok/Broadcast.java | 32 +++++++++++ .../java/com/opentok/BroadcastProperties.java | 53 ++++++++++++++----- src/main/java/com/opentok/OpenTok.java | 1 - src/main/java/com/opentok/Rtmp.java | 14 ++++- src/main/java/com/opentok/SipProperties.java | 53 +++++++++++++++---- .../java/com/opentok/constants/Version.java | 2 +- .../java/com/opentok/util/HttpClient.java | 12 +++++ .../java/com/opentok/test/OpenTokTest.java | 34 +++++++----- 11 files changed, 167 insertions(+), 46 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9a0040d6..c101e7cc 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = True tag = False -current_version = v4.11.0 +current_version = v4.12.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))? serialize = {major}.{minor}.{patch}-{release}{build} diff --git a/README.md b/README.md index fcdde081..8f9bb8f0 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ When you use Maven as your build tool, you can manage dependencies in the `pom.x com.tokbox opentok-server-sdk - 4.11.0 + 4.12.0 ``` @@ -44,7 +44,7 @@ When you use Gradle as your build tool, you can manage dependencies in the `buil ```groovy dependencies { - compile group: 'com.tokbox', name: 'opentok-server-sdk', version: '4.11.0' + compile group: 'com.tokbox', name: 'opentok-server-sdk', version: '4.12.0' } ``` diff --git a/build.gradle b/build.gradle index 4d58d06f..87ec9ef1 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { group = 'com.tokbox' archivesBaseName = 'opentok-server-sdk' -version = '4.11.0' +version = '4.12.0' sourceCompatibility = "1.8" targetCompatibility = "1.8" @@ -30,8 +30,8 @@ dependencies { implementation 'commons-lang:commons-lang:2.6' implementation 'commons-codec:commons-codec:1.16.0' - implementation 'io.netty:netty-codec-http:4.1.94.Final' - implementation 'io.netty:netty-handler:4.1.94.Final' + implementation 'io.netty:netty-codec-http:4.1.96.Final' + implementation 'io.netty:netty-handler:4.1.96.Final' implementation 'org.asynchttpclient:async-http-client:2.12.3' implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'org.bitbucket.b_c:jose4j:0.9.3' diff --git a/src/main/java/com/opentok/Broadcast.java b/src/main/java/com/opentok/Broadcast.java index f0d3d134..596f18ce 100644 --- a/src/main/java/com/opentok/Broadcast.java +++ b/src/main/java/com/opentok/Broadcast.java @@ -52,8 +52,11 @@ public String toString() { @JsonProperty private int projectId; @JsonProperty private long createdAt; @JsonProperty private long updatedAt; + @JsonProperty private int maxDuration; + @JsonProperty private int maxBitrate; @JsonProperty private String resolution; @JsonProperty private String status; + @JsonProperty private String hlsStatus; @JsonProperty private String multiBroadcastTag; @JsonProperty private boolean hasAudio = true; @JsonProperty private boolean hasVideo = true; @@ -112,6 +115,24 @@ public long getUpdatedAt() { return updatedAt; } + /** + * The maximum duration of the broadcast in seconds. + * + * @return The maximum duration. + */ + public int getMaxDuration() { + return maxDuration; + } + + /** + * Maximum bitrate (bits per second) is an optional value allowed for the broadcast composing. + * + * @return The maximum bitrate. + */ + public int getMaxBitrate() { + return maxBitrate; + } + /** * The broadcast resolution. */ @@ -145,6 +166,7 @@ public String getMultiBroadcastTag() { private void unpack(Map broadcastUrls) { if (broadcastUrls == null) return; hls = (String) broadcastUrls.get("hls"); + hlsStatus = (String) broadcastUrls.get("hlsStatus"); Iterable> rtmpResponse = (Iterable>)broadcastUrls.get("rtmp"); if (rtmpResponse == null) return; for (Map element : rtmpResponse) { @@ -152,6 +174,7 @@ private void unpack(Map broadcastUrls) { rtmp.setId(element.get("id")); rtmp.setServerUrl(element.get("serverUrl")); rtmp.setStreamName(element.get("streamName")); + rtmp.setStatus(element.get("status")); this.rtmpList.add(rtmp); } } @@ -163,6 +186,15 @@ public String getHls() { return hls; } + /** + * The HLS status of the broadcast if known. One of: "connecting", "ready", "live", "ended", "error". + * + * @return The HLS status as a string (if applicable). + */ + public String getHlsStatus() { + return hlsStatus; + } + /** * A list of RTMP URLs (if there are any) of the broadcast. */ diff --git a/src/main/java/com/opentok/BroadcastProperties.java b/src/main/java/com/opentok/BroadcastProperties.java index e55c2db3..d1d65c5a 100644 --- a/src/main/java/com/opentok/BroadcastProperties.java +++ b/src/main/java/com/opentok/BroadcastProperties.java @@ -21,6 +21,7 @@ public class BroadcastProperties { private final BroadcastLayout layout; private final int maxDuration; + private final int maxBitrate; private final boolean hasHls; private final boolean hasAudio; private final boolean hasVideo; @@ -31,16 +32,17 @@ public class BroadcastProperties { private final Hls hls; private BroadcastProperties(Builder builder) { - this.layout = builder.layout; - this.maxDuration = builder.maxDuration; - this.hasHls = builder.hasHls; - this.hasAudio = builder.hasAudio; - this.hasVideo = builder.hasVideo; - this.hls = builder.hls; - this.rtmpList = builder.rtmpList; - this.resolution = builder.resolution; - this.streamMode = builder.streamMode; - this.multiBroadcastTag = builder.multiBroadcastTag; + layout = builder.layout; + maxDuration = builder.maxDuration; + maxBitrate = builder.maxBitrate; + hasHls = builder.hasHls; + hasAudio = builder.hasAudio; + hasVideo = builder.hasVideo; + hls = builder.hls; + rtmpList = builder.rtmpList; + resolution = builder.resolution; + streamMode = builder.streamMode; + multiBroadcastTag = builder.multiBroadcastTag; } /** @@ -51,6 +53,7 @@ private BroadcastProperties(Builder builder) { public static class Builder { private BroadcastLayout layout = new BroadcastLayout(BroadcastLayout.Type.BESTFIT); private int maxDuration = 7200; + private int maxBitrate = 2_000_000; private boolean hasHls = false; private boolean hasAudio = true; private boolean hasVideo = true; @@ -67,7 +70,7 @@ public static class Builder { * * @return The BroadcastProperties.Builder object with the layout setting. */ - public Builder layout(BroadcastLayout layout){ + public Builder layout(BroadcastLayout layout) { this.layout = layout; return this; } @@ -82,14 +85,31 @@ public Builder layout(BroadcastLayout layout){ * * @return The BroadcastProperties.Builder object with the maxDuration setting. */ - public Builder maxDuration(int maxDuration) throws InvalidArgumentException { - if (maxDuration < 60 || maxDuration > 36000) { + public Builder maxDuration(int maxDuration) throws InvalidArgumentException { + if (maxDuration < 60 || maxDuration > 36_000) { throw new InvalidArgumentException("maxDuration value must be between 60 and 36000 (inclusive)."); } this.maxDuration = maxDuration; return this; } + /** + * Sets the maximum bitrate in bits per second for broadcast composing. + * + * @param maxBitrate The maximum bitrate in bits per second. + * + * @return The BroadcastProperties.Builder object with the maxBitrate setting. + * + * @throws InvalidArgumentException If the bitrate is out of bounds. + */ + public Builder maxBitrate(int maxBitrate) throws InvalidArgumentException { + if (maxBitrate < 100_000 || maxBitrate > 6_000_000) { + throw new InvalidArgumentException("maxBitrate value must be between 100_000 and 6_000_000."); + } + this.maxBitrate = maxBitrate; + return this; + } + /** * Call this method to include an HLS broadcast (true) or not false). * @@ -229,6 +249,13 @@ public int maxDuration() { return maxDuration; } + /** + * The maximum bitrate in bits per second of the broadcast. + */ + public int maxBitrate() { + return maxBitrate; + } + /** * Whether the broadcast has HLS (true) or not (false). */ diff --git a/src/main/java/com/opentok/OpenTok.java b/src/main/java/com/opentok/OpenTok.java index da35665d..1d80849b 100644 --- a/src/main/java/com/opentok/OpenTok.java +++ b/src/main/java/com/opentok/OpenTok.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.Proxy; -import java.util.Collection; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/opentok/Rtmp.java b/src/main/java/com/opentok/Rtmp.java index bc539cd4..1cb261c4 100644 --- a/src/main/java/com/opentok/Rtmp.java +++ b/src/main/java/com/opentok/Rtmp.java @@ -13,11 +13,11 @@ * Represents an RTMP stream in an OpenTok session. */ @JsonIgnoreProperties(ignoreUnknown=true) - public class Rtmp { public String id; private String serverUrl; private String streamName; + private String status; /** * The stream ID. @@ -28,6 +28,7 @@ public void setId(String id) { public String getId() { return id; } + /** * The RTMP server URL. */ @@ -37,6 +38,7 @@ public void setServerUrl(String serverUrl) { public String getServerUrl() { return serverUrl; } + /** * The stream name. */ @@ -46,5 +48,15 @@ public void setStreamName(String streamName) { public String getStreamName() { return streamName; } + + /** + * @return The RTMP status. + */ + public String getStatus() { + return status; + } + public void setStatus(String status) { + this.status = status; + } } diff --git a/src/main/java/com/opentok/SipProperties.java b/src/main/java/com/opentok/SipProperties.java index 87034d21..e7d93d28 100644 --- a/src/main/java/com/opentok/SipProperties.java +++ b/src/main/java/com/opentok/SipProperties.java @@ -7,6 +7,9 @@ */ package com.opentok; +import java.util.Arrays; +import java.util.List; + /** * Defines values for the properties parameter of the * {@link OpenTok#dial(String, String, SipProperties)} method. @@ -22,16 +25,18 @@ public class SipProperties { private Boolean secure; private Boolean video; private Boolean observeForceMute; + private String[] streams; private SipProperties(Builder builder) { - this.sipUri = builder.sipUri; - this.from = builder.from; - this.userName = builder.userName; - this.password = builder.password; - this.headersJsonStartingWithXDash = builder.headersJsonStartingWithXDash; - this.secure = builder.secure; - this.video = builder.video; - this.observeForceMute = builder.observeForceMute; + sipUri = builder.sipUri; + from = builder.from; + userName = builder.userName; + password = builder.password; + headersJsonStartingWithXDash = builder.headersJsonStartingWithXDash; + secure = builder.secure; + video = builder.video; + observeForceMute = builder.observeForceMute; + streams = builder.streams; } /** @@ -48,7 +53,7 @@ public static class Builder { private boolean secure = false; private boolean video = false; private boolean observeForceMute = false; - + private String[] streams = null; /** * Call this method to set the SIP URI. @@ -167,6 +172,19 @@ public Builder observeForceMute(boolean observeForceMute) { return this; } + /** + * The stream IDs of the participants' which will be subscribed by the SIP participant. + * If not provided, all streams in session will be selected. + * + * @param streams Stream IDs to select. + * + * @return The SipProperties.Builder object with the streams setting. + */ + public Builder streams(String... streams) { + this.streams = streams; + return this; + } + /** * Builds the SipProperties object. * @@ -222,10 +240,23 @@ public boolean secure() { /** * Return the video value (true or false). */ - public boolean video() { return video; } + public boolean video() { + return video; + } /** * Returns the observeForceMute value (true or false). */ - public boolean observeForceMute() { return observeForceMute; } + public boolean observeForceMute() { + return observeForceMute; + } + + /** + * Returns the subscribed stream IDs. + * + * @return The selected stream IDs as an array. + */ + public String[] streams() { + return streams; + } } diff --git a/src/main/java/com/opentok/constants/Version.java b/src/main/java/com/opentok/constants/Version.java index ceb77535..07129385 100644 --- a/src/main/java/com/opentok/constants/Version.java +++ b/src/main/java/com/opentok/constants/Version.java @@ -8,5 +8,5 @@ package com.opentok.constants; public class Version { - public static final String VERSION = "4.11.0"; + public static final String VERSION = "4.12.0"; } diff --git a/src/main/java/com/opentok/util/HttpClient.java b/src/main/java/com/opentok/util/HttpClient.java index bb058df3..a257d241 100644 --- a/src/main/java/com/opentok/util/HttpClient.java +++ b/src/main/java/com/opentok/util/HttpClient.java @@ -513,6 +513,9 @@ public String startBroadcast(String sessionId, BroadcastProperties properties) t if (properties.maxDuration() > 0) { requestJson.put("maxDuration", properties.maxDuration()); } + if (properties.maxBitrate() > 0) { + requestJson.put("maxBitrate", properties.maxBitrate()); + } if (properties.resolution() != null) { requestJson.put("resolution", properties.resolution()); } @@ -822,6 +825,15 @@ public String sipDial(String sessionId, String token, SipProperties props) throw jGenerator.writeFieldName("observeForceMute"); jGenerator.writeBoolean(props.observeForceMute()); + String[] streams = props.streams(); + if (streams != null && streams.length > 0) { + jGenerator.writeArrayFieldStart("streams"); + for (String streamId : streams) { + jGenerator.writeString(streamId); + } + jGenerator.writeEndArray(); + } + jGenerator.writeEndObject(); // end sip jGenerator.writeEndObject(); // end main object jGenerator.close(); diff --git a/src/test/java/com/opentok/test/OpenTokTest.java b/src/test/java/com/opentok/test/OpenTokTest.java index 1ce59d57..286cade7 100644 --- a/src/test/java/com/opentok/test/OpenTokTest.java +++ b/src/test/java/com/opentok/test/OpenTokTest.java @@ -21,7 +21,6 @@ import com.opentok.exception.OpenTokException; import com.opentok.exception.RequestException; import org.apache.commons.lang.StringUtils; -import org.checkerframework.common.returnsreceiver.qual.This; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -1779,8 +1778,8 @@ public void testStartBroadcast() throws OpenTokException { stubFor(post(urlEqualTo(url)) .withRequestBody(equalTo("{\"sessionId\":\"SESSIONID\",\"streamMode\":\"auto\"," + "\"hasAudio\":false,\"hasVideo\":false,\"layout\":{\"type\":\"pip\"},\"maxDuration\":1000," + - "\"resolution\":\"1920x1080\",\"multiBroadcastTag\":\"MyVideoBroadcastTag\",\"outputs\":{" + - "\"hls\":{},\"rtmp\":[{\"id\":\"foo\",\"serverUrl\":\"rtmp://myfooserver/myfooapp\"," + + "\"maxBitrate\":524288,\"resolution\":\"1920x1080\",\"multiBroadcastTag\":\"MyVideoBroadcastTag\"," + + "\"outputs\":{\"hls\":{},\"rtmp\":[{\"id\":\"foo\",\"serverUrl\":\"rtmp://myfooserver/myfooapp\"," + "\"streamName\":\"myfoostream\"},{\"id\":\"bar\",\"serverUrl\":" + "\"rtmp://mybarserver/mybarapp\",\"streamName\":\"mybarstream\"}]}}" )) @@ -1792,7 +1791,9 @@ public void testStartBroadcast() throws OpenTokException { " \"sessionId\" : \"SESSIONID\",\n" + " \"projectId\" : 123456,\n" + " \"createdAt\" : 1437676551000,\n" + - " \"updatedAt\" : 1437676551000,\n" + + " \"updatedAt\" : 1447676551000,\n" + + " \"maxDuration\": 5400,\n" + + " \"maxBitrate\": 7234560,\n" + " \"hasAudio\" : false,\n" + " \"hasVideo\" : false,\n" + " \"resolution\" : \"1280x720\",\n" + @@ -1800,15 +1801,17 @@ public void testStartBroadcast() throws OpenTokException { " \"multiBroadcastTag\" : \"MyVideoBroadcastTag\",\n" + " \"broadcastUrls\" : {" + " \"hls\" : \"http://server/fakepath/playlist.m3u8\"," + + " \"hlsStatus\" : \"ready\"," + " \"rtmp\" : [{" + " \"id\" : \"foo\"," + " \"serverUrl\" : \"rtmp://myfooserver/myfooapp\"," + - " \"streamName\" : \"myfoostream\"" + - " }," + - " { " + + " \"streamName\" : \"myfoostream\"," + + " \"status\" : \"live\"" + + " },{" + " \"id\" : \"bar\"," + " \"serverUrl\" : \"rtmp://mybarserver/mybarapp\"," + - " \"streamName\" : \"mybarstream\"" + + " \"streamName\" : \"mybarstream\"," + + " \"status\" : \"offline\"" + " }]" + " }" + " }" + @@ -1823,6 +1826,7 @@ public void testStartBroadcast() throws OpenTokException { .addRtmpProperties(rtmpProps) .addRtmpProperties(rtmpNextProps) .maxDuration(1000) + .maxBitrate(524288) .resolution("1920x1080") .hasAudio(false).hasVideo(false) .multiBroadcastTag("MyVideoBroadcastTag") @@ -1838,13 +1842,15 @@ public void testStartBroadcast() throws OpenTokException { assertNotNull(rtmp.getServerUrl()); assertNotNull(rtmp.getStreamName()); assertNotNull(broadcast.toString()); - assertNotNull(broadcast.getStatus()); + assertEquals("started", broadcast.getStatus()); assertFalse(broadcast.hasAudio()); assertFalse(broadcast.hasVideo()); + assertEquals(7234560, broadcast.getMaxBitrate()); + assertEquals(5400, broadcast.getMaxDuration()); assertEquals("1280x720", broadcast.getResolution()); - assertTrue(broadcast.getCreatedAt() > 0); - assertTrue(broadcast.getUpdatedAt() > -1); - assertTrue(broadcast.getProjectId() > -1); + assertEquals(1437676551000L, broadcast.getCreatedAt()); + assertEquals(1447676551000L, broadcast.getUpdatedAt()); + assertEquals(123456, broadcast.getProjectId()); assertEquals(sessionId, broadcast.getSessionId()); assertEquals("MyVideoBroadcastTag", broadcast.getMultiBroadcastTag()); assertEquals(Broadcast.StreamMode.AUTO, broadcast.getStreamMode()); @@ -1931,6 +1937,7 @@ public void testBroadcastWithScreenShareType() throws OpenTokException { broadcastRootNode.put("updatedAt", "1437676551000"); broadcastRootNode.put("status", "started"); broadcastRootNode.put("maxDuration", "5400"); + broadcastRootNode.put("maxBitrate", "2000000"); broadcastRootNode.put("resolution", "1280x720"); broadcastRootNode.put("partnerId", "12345678"); broadcastRootNode.put("event", "broadcast"); @@ -1948,7 +1955,7 @@ public void testBroadcastWithScreenShareType() throws OpenTokException { .maxDuration(5400) .layout(layout) .build(); - String expectedJson = String.format("{\"sessionId\":\"%s\",\"streamMode\":\"auto\",\"hasAudio\":true,\"hasVideo\":true,\"layout\":{\"type\":\"bestFit\",\"screenshareType\":\"pip\"},\"maxDuration\":5400,\"resolution\":\"640x480\",\"outputs\":{\"hls\":{},\"rtmp\":[]}}",sessionId); + String expectedJson = String.format("{\"sessionId\":\"%s\",\"streamMode\":\"auto\",\"hasAudio\":true,\"hasVideo\":true,\"layout\":{\"type\":\"bestFit\",\"screenshareType\":\"pip\"},\"maxDuration\":5400,\"maxBitrate\":2000000,\"resolution\":\"640x480\",\"outputs\":{\"hls\":{},\"rtmp\":[]}}",sessionId); Broadcast broadcast = sdk.startBroadcast(sessionId, properties); assertNotNull(broadcast); assertEquals(sessionId, broadcast.getSessionId()); @@ -2339,6 +2346,7 @@ public void testSipDial() throws OpenTokException { .password("password") .secure(true) .video(true) + .streams("Stream ID 1", "STREAM_ID2") .observeForceMute(true) .build(); Sip sip = sdk.dial(sessionId, token, properties);