diff --git a/openvidu-client/src/main/java/io/openvidu/client/OpenViduException.java b/openvidu-client/src/main/java/io/openvidu/client/OpenViduException.java
index c6757c23..c2c47515 100644
--- a/openvidu-client/src/main/java/io/openvidu/client/OpenViduException.java
+++ b/openvidu-client/src/main/java/io/openvidu/client/OpenViduException.java
@@ -49,7 +49,9 @@ public class OpenViduException extends JsonRpcErrorException {
DOCKER_NOT_FOUND(709), RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707),
RECORDING_DELETE_ERROR_CODE(706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704),
- RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701);
+ RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701),
+
+ FORCED_CODEC_NOT_FOUND_IN_SDPOFFER(800);
private int value;
diff --git a/openvidu-java-client/generate-docs.sh b/openvidu-java-client/generate-docs.sh
index 3abb098a..e686c5fe 100755
--- a/openvidu-java-client/generate-docs.sh
+++ b/openvidu-java-client/generate-docs.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/bash -x
if [[ -z "$BASEHREF_VERSION" ]]; then
echo "Example of use: \"BASEHREF_VERSION=2.12.0 ${0}\"" 1>&2
@@ -9,7 +9,7 @@ fi
grep -rl '/en/stable/' ./src | xargs sed -i -e 's|/en/stable/|/en/'${BASEHREF_VERSION}'/|g'
# Generate JavaDoc
-mvn javadoc:javadoc
+mvn javadoc:javadoc -DadditionalJOption=-Xdoclint:none
rm -rf ../../openvidu.io/api/openvidu-java-client/*
cp -R ./target/site/apidocs/. ../../openvidu.io-docs/docs/api/openvidu-java-client
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java
index c2e3bcc4..8b7da5d8 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java
@@ -25,6 +25,11 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
+
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
@@ -35,11 +40,6 @@ import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSyntaxException;
-
public class Session {
private static final Logger log = LoggerFactory.getLogger(Session.class);
@@ -458,6 +458,8 @@ public class Session {
json.addProperty("defaultRecordingLayout", properties.defaultRecordingLayout().name());
json.addProperty("defaultCustomLayout", properties.defaultCustomLayout());
json.addProperty("customSessionId", properties.customSessionId());
+ json.addProperty("forcedVideoCodec", properties.forcedVideoCodec().name());
+ json.addProperty("allowTranscoding", properties.isTranscodingAllowed());
StringEntity params = null;
try {
params = new StringEntity(json.toString());
@@ -520,6 +522,12 @@ public class Session {
if (json.has("defaultCustomLayout")) {
builder.defaultCustomLayout(json.get("defaultCustomLayout").getAsString());
}
+ if (json.has("forcedVideoCodec")) {
+ builder.forcedVideoCodec(VideoCodec.valueOf(json.get("forcedVideoCodec").getAsString()));
+ }
+ if (json.has("allowTranscoding")) {
+ builder.allowTranscoding(json.get("allowTranscoding").getAsBoolean());
+ }
if (this.properties != null && this.properties.customSessionId() != null) {
builder.customSessionId(this.properties.customSessionId());
} else if (json.has("customSessionId")) {
@@ -572,6 +580,9 @@ public class Session {
json.addProperty("defaultOutputMode", this.properties.defaultOutputMode().name());
json.addProperty("defaultRecordingLayout", this.properties.defaultRecordingLayout().name());
json.addProperty("defaultCustomLayout", this.properties.defaultCustomLayout());
+ json.addProperty("forcedVideoCodec", this.properties.forcedVideoCodec().name());
+ json.addProperty("allowTranscoding", this.properties.isTranscodingAllowed());
+
JsonObject connections = new JsonObject();
connections.addProperty("numberOfElements", this.getActiveConnections().size());
JsonArray jsonArrayConnections = new JsonArray();
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/SessionProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/SessionProperties.java
index 8f436721..59842409 100644
--- a/openvidu-java-client/src/main/java/io/openvidu/java/client/SessionProperties.java
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/SessionProperties.java
@@ -30,6 +30,8 @@ public class SessionProperties {
private RecordingLayout defaultRecordingLayout;
private String defaultCustomLayout;
private String customSessionId;
+ private VideoCodec forcedVideoCodec;
+ private boolean allowTranscoding;
/**
* Builder for {@link io.openvidu.java.client.SessionProperties}
@@ -42,6 +44,8 @@ public class SessionProperties {
private RecordingLayout defaultRecordingLayout = RecordingLayout.BEST_FIT;
private String defaultCustomLayout = "";
private String customSessionId = "";
+ private VideoCodec forcedVideoCodec = VideoCodec.VP8;
+ private boolean allowTranscoding = false;
/**
* Returns the {@link io.openvidu.java.client.SessionProperties} object properly
@@ -49,7 +53,8 @@ public class SessionProperties {
*/
public SessionProperties build() {
return new SessionProperties(this.mediaMode, this.recordingMode, this.defaultOutputMode,
- this.defaultRecordingLayout, this.defaultCustomLayout, this.customSessionId);
+ this.defaultRecordingLayout, this.defaultCustomLayout, this.customSessionId,
+ this.forcedVideoCodec, this.allowTranscoding);
}
/**
@@ -137,6 +142,34 @@ public class SessionProperties {
this.customSessionId = customSessionId;
return this;
}
+
+ /**
+ *
+ * Call this method to define which video codec do you want to be forcibly used for this session.
+ * This allows browsers to use the same codec avoiding transcoding in the media server.
+ * To force this video codec you need to set {@link #allowTranscoding(boolean)} to false
.
+ *
+ */
+ public SessionProperties.Builder forcedVideoCodec(VideoCodec forcedVideoCodec) {
+ this.forcedVideoCodec = forcedVideoCodec;
+ return this;
+ }
+
+ /**
+ *
+ * Call this method to define if you want to allowTranscoding or not. If you define it as
+ * as false
, the default video codec VP8 will be used for all browsers, and the media
+ * server will not do any transcoding. If you define it as true
, transcoding can be
+ * executed by the media server when necessary.
+ *
+ * If you want to set a different video codec, you can configure it
+ * by calling {@link #forcedVideoCodec(VideoCodec)} to your preferred one.
+ *
+ */
+ public SessionProperties.Builder allowTranscoding(boolean allowTranscoding) {
+ this.allowTranscoding = allowTranscoding;
+ return this;
+ }
}
@@ -147,16 +180,21 @@ public class SessionProperties {
this.defaultRecordingLayout = RecordingLayout.BEST_FIT;
this.defaultCustomLayout = "";
this.customSessionId = "";
+ this.forcedVideoCodec = VideoCodec.VP8;
+ this.allowTranscoding = false;
}
private SessionProperties(MediaMode mediaMode, RecordingMode recordingMode, OutputMode outputMode,
- RecordingLayout layout, String defaultCustomLayout, String customSessionId) {
+ RecordingLayout layout, String defaultCustomLayout, String customSessionId,
+ VideoCodec forcedVideoCodec, boolean allowTranscoding) {
this.mediaMode = mediaMode;
this.recordingMode = recordingMode;
this.defaultOutputMode = outputMode;
this.defaultRecordingLayout = layout;
this.defaultCustomLayout = defaultCustomLayout;
this.customSessionId = customSessionId;
+ this.forcedVideoCodec = forcedVideoCodec;
+ this.allowTranscoding = allowTranscoding;
}
/**
@@ -230,5 +268,25 @@ public class SessionProperties {
public String customSessionId() {
return this.customSessionId;
}
+
+ /**
+ *
+ * Defines which video codec is being forced to be used when
+ * {@link io.openvidu.java.client.SessionProperties.Builder#allowTranscoding(boolean)}
+ * has been set to false
+ */
+ public VideoCodec forcedVideoCodec() {
+ return this.forcedVideoCodec;
+ }
+
+ /**
+ *
+ * Defines if transcoding is allowed or not. If this method returns false
, a video codec
+ * will be forcibly used for all browsers (See
+ * {@link io.openvidu.java.client.SessionProperties.Builder#forcedVideoCodec(VideoCodec)}).
+ */
+ public boolean isTranscodingAllowed() {
+ return this.allowTranscoding;
+ }
}
\ No newline at end of file
diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/VideoCodec.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/VideoCodec.java
new file mode 100644
index 00000000..9f6e002f
--- /dev/null
+++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/VideoCodec.java
@@ -0,0 +1,25 @@
+/*
+ * (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package io.openvidu.java.client;
+
+/**
+ * See {@link io.openvidu.java.client.SessionProperties.Builder#forcedVideoCodec(VideoCodec)}
+ */
+public enum VideoCodec {
+ VP8, VP9, H264
+}
\ No newline at end of file
diff --git a/openvidu-node-client/src/Session.ts b/openvidu-node-client/src/Session.ts
index a9fead7b..3b5b4486 100644
--- a/openvidu-node-client/src/Session.ts
+++ b/openvidu-node-client/src/Session.ts
@@ -26,6 +26,7 @@ import { RecordingLayout } from './RecordingLayout';
import { RecordingMode } from './RecordingMode';
import { SessionProperties } from './SessionProperties';
import { TokenOptions } from './TokenOptions';
+import { VideoCodec } from './VideoCodec';
export class Session {
@@ -83,6 +84,8 @@ export class Session {
this.properties.recordingMode = !!this.properties.recordingMode ? this.properties.recordingMode : RecordingMode.MANUAL;
this.properties.defaultOutputMode = !!this.properties.defaultOutputMode ? this.properties.defaultOutputMode : Recording.OutputMode.COMPOSED;
this.properties.defaultRecordingLayout = !!this.properties.defaultRecordingLayout ? this.properties.defaultRecordingLayout : RecordingLayout.BEST_FIT;
+ this.properties.allowTranscoding = !!this.properties.allowTranscoding ? this.properties.allowTranscoding : false;
+ this.properties.forcedVideoCodec = !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : VideoCodec.VP8;
}
/**
@@ -401,7 +404,10 @@ export class Session {
defaultOutputMode: !!this.properties.defaultOutputMode ? this.properties.defaultOutputMode : Recording.OutputMode.COMPOSED,
defaultRecordingLayout: !!this.properties.defaultRecordingLayout ? this.properties.defaultRecordingLayout : RecordingLayout.BEST_FIT,
defaultCustomLayout: !!this.properties.defaultCustomLayout ? this.properties.defaultCustomLayout : '',
- customSessionId: !!this.properties.customSessionId ? this.properties.customSessionId : ''
+ customSessionId: !!this.properties.customSessionId ? this.properties.customSessionId : '',
+ forcedVideoCodec: !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : VideoCodec.VP8,
+ allowTranscoding: !!this.properties.allowTranscoding ? this.properties.allowTranscoding : false
+
});
axios.post(
@@ -466,7 +472,9 @@ export class Session {
mediaMode: json.mediaMode,
recordingMode: json.recordingMode,
defaultOutputMode: json.defaultOutputMode,
- defaultRecordingLayout: json.defaultRecordingLayout
+ defaultRecordingLayout: json.defaultRecordingLayout,
+ forcedVideoCodec: !!json.forcedVideoCodec ? json.forcedVideoCodec : VideoCodec.VP8,
+ allowTranscoding: !!json.allowTranscoding ? json.allowTranscoding : false
};
if (!!customSessionId) {
this.properties.customSessionId = customSessionId;
diff --git a/openvidu-node-client/src/SessionProperties.ts b/openvidu-node-client/src/SessionProperties.ts
index f866e1b8..df45ac41 100644
--- a/openvidu-node-client/src/SessionProperties.ts
+++ b/openvidu-node-client/src/SessionProperties.ts
@@ -64,4 +64,24 @@ export interface SessionProperties {
* If this parameter is undefined or an empty string, OpenVidu Server will generate a random sessionId for you.
*/
customSessionId?: string;
+
+ /**
+ * Call this method to define which video codec do you want to be forcibly used for this session.
+ * This allows browsers to use the same codec avoiding transcoding in the media server.
+ * To force this video codec you need to set [[allowTranscoding]] to false
.
+ */
+ forcedVideoCodec?: string;
+
+ /**
+ * Call this method to define if you want to allowTranscoding or not. If you define it as
+ * as false
, the default video codec VP8 will be used for all browsers, and the media
+ * server will not do any transcoding. If you define it as true
, transcoding can be
+ * executed by the media server when necessary.
+ *
+ * If you want to set a different video codec, you can configure it
+ * by calling [[forcedVideoCodec]] to your preferred one.
+ *
+ */
+ allowTranscoding?: boolean;
+
}
diff --git a/openvidu-node-client/src/VideoCodec.ts b/openvidu-node-client/src/VideoCodec.ts
new file mode 100644
index 00000000..e1077f77
--- /dev/null
+++ b/openvidu-node-client/src/VideoCodec.ts
@@ -0,0 +1,10 @@
+/**
+ * See [[SessionProperties.forcedVideoCodec]]
+ */
+export enum VideoCodec {
+
+ VP8 = 'VP8',
+ VP9 = 'VP9',
+ H264 = 'H264'
+
+}
\ No newline at end of file
diff --git a/openvidu-node-client/src/index.ts b/openvidu-node-client/src/index.ts
index 50ce477a..4113ac84 100644
--- a/openvidu-node-client/src/index.ts
+++ b/openvidu-node-client/src/index.ts
@@ -9,4 +9,5 @@ export * from './RecordingMode';
export * from './Recording';
export * from './RecordingProperties';
export * from './Connection';
-export * from './Publisher';
\ No newline at end of file
+export * from './Publisher';
+export * from './VideoCodec';
\ No newline at end of file
diff --git a/openvidu-server/deployments/ce/docker-compose/.env b/openvidu-server/deployments/ce/docker-compose/.env
index 279ec85e..29ac0cb5 100644
--- a/openvidu-server/deployments/ce/docker-compose/.env
+++ b/openvidu-server/deployments/ce/docker-compose/.env
@@ -149,7 +149,7 @@ OPENVIDU_CDR_PATH=/opt/openvidu/cdr
# --------------------------
# Docker hub kurento media server: https://hub.docker.com/r/kurento/kurento-media-server-dev
# Uncomment the next line and define this variable with KMS image that you want use
-# KMS_IMAGE=kurento/kurento-media-server-dev:6.14.0
+# KMS_IMAGE=kurento/kurento-media-server:6.14.0
# Kurento Media Server Level logs
# -------------------------------
diff --git a/openvidu-server/deployments/pro/docker-compose/media-node/.env b/openvidu-server/deployments/pro/docker-compose/media-node/.env
index 704c9211..8108613b 100644
--- a/openvidu-server/deployments/pro/docker-compose/media-node/.env
+++ b/openvidu-server/deployments/pro/docker-compose/media-node/.env
@@ -8,7 +8,7 @@
# --------------------------
# Docker hub kurento media server: https://hub.docker.com/r/kurento/kurento-media-server-dev
# Uncomment the next line and define this variable with KMS image that you want use
-# KMS_IMAGE=kurento/kurento-media-server-dev:6.14.0
+# KMS_IMAGE=kurento/kurento-media-server:6.14.0
# Kurento Media Server Level logs
# -------------------------------
diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java
index ee2d8cbe..a81943f1 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java
@@ -72,6 +72,7 @@ import io.openvidu.server.utils.MediaNodeStatusManager;
import io.openvidu.server.utils.MediaNodeStatusManagerDummy;
import io.openvidu.server.utils.QuarantineKiller;
import io.openvidu.server.utils.QuarantineKillerDummy;
+import io.openvidu.server.utils.SDPMunging;
import io.openvidu.server.webhook.CDRLoggerWebhook;
/**
@@ -195,6 +196,12 @@ public class OpenViduServer implements JsonRpcConfigurer {
public GeoLocationByIp geoLocationByIp() {
return new GeoLocationByIpDummy();
}
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SDPMunging sdpMunging() {
+ return new SDPMunging();
+ }
@Bean
@ConditionalOnMissingBean
diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/Session.java b/openvidu-server/src/main/java/io/openvidu/server/core/Session.java
index 7edf339c..bf0c0807 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/core/Session.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/core/Session.java
@@ -29,12 +29,12 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
@@ -200,6 +200,8 @@ public class Session implements SessionInterface {
json.addProperty("mediaMode", this.sessionProperties.mediaMode().name());
json.addProperty("recordingMode", this.sessionProperties.recordingMode().name());
json.addProperty("defaultOutputMode", this.sessionProperties.defaultOutputMode().name());
+ json.addProperty("forcedVideoCodec", this.sessionProperties.forcedVideoCodec().name());
+ json.addProperty("allowTranscoding", this.sessionProperties.isTranscodingAllowed());
if (RecordingUtils.IS_COMPOSED(this.sessionProperties.defaultOutputMode())) {
json.addProperty("defaultRecordingLayout", this.sessionProperties.defaultRecordingLayout().name());
if (RecordingLayout.CUSTOM.equals(this.sessionProperties.defaultRecordingLayout())) {
diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
index af55da6e..a2e0411b 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java
@@ -32,17 +32,17 @@ import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
import org.apache.commons.lang3.RandomStringUtils;
import org.kurento.jsonrpc.message.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
-
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java
index 681941ea..e546ff5e 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java
@@ -30,6 +30,9 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
import org.apache.commons.lang3.RandomStringUtils;
import org.kurento.client.GenericMediaElement;
import org.kurento.client.IceCandidate;
@@ -41,9 +44,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
@@ -53,6 +53,7 @@ import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingMode;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.SessionProperties;
+import io.openvidu.java.client.VideoCodec;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.FinalUser;
import io.openvidu.server.core.IdentifierPrefixes;
@@ -68,6 +69,7 @@ import io.openvidu.server.kurento.kms.KmsManager;
import io.openvidu.server.rpc.RpcHandler;
import io.openvidu.server.utils.GeoLocation;
import io.openvidu.server.utils.JsonUtils;
+import io.openvidu.server.utils.SDPMunging;
public class KurentoSessionManager extends SessionManager {
@@ -81,6 +83,9 @@ public class KurentoSessionManager extends SessionManager {
@Autowired
private KurentoParticipantEndpointConfig kurentoEndpointConfig;
+
+ @Autowired
+ private SDPMunging sdpMunging;
@Override
/* Protected by Session.closingLock.readLock */
@@ -365,15 +370,29 @@ public class KurentoSessionManager extends SessionManager {
KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) mediaOptions;
KurentoParticipant kParticipant = (KurentoParticipant) participant;
-
+ KurentoSession kSession = kParticipant.getSession();
+
+ // Modify sdp if transcoding is not allowed
+ if(!kSession.getSessionProperties().isTranscodingAllowed()) {
+ VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
+ String sdpOffer = kurentoOptions.sdpOffer;
+
+ try {
+ kurentoOptions.sdpOffer = modifySdpToForceCodec(forcedVideoCodec, sdpOffer);
+ } catch (OpenViduException e) {
+ String errorMessage = "Error forcing codec: ''" + forcedVideoCodec + "', for publisher on Session: '" + kSession.getSessionId()
+ + "'\nException: " + e.getMessage() + "\nSDP:\n" + sdpOffer;
+ throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, errorMessage);
+ }
+
+ }
+
log.debug(
"Request [PUBLISH_MEDIA] isOffer={} sdp={} "
+ "loopbackAltSrc={} lpbkConnType={} doLoopback={} rtspUri={} ({})",
kurentoOptions.isOffer, kurentoOptions.sdpOffer, kurentoOptions.doLoopback, kurentoOptions.rtspUri,
participant.getParticipantPublicId());
- KurentoSession kSession = kParticipant.getSession();
-
kParticipant.createPublishingEndpoint(mediaOptions, null);
/*
@@ -397,7 +416,7 @@ public class KurentoSessionManager extends SessionManager {
throw e;
}
}
-
+
sdpAnswer = kParticipant.publishToRoom(kurentoOptions.sdpOffer, kurentoOptions.doLoopback, false);
if (sdpAnswer == null) {
@@ -558,6 +577,18 @@ public class KurentoSessionManager extends SessionManager {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
session = ((KurentoParticipant) participant).getSession();
Participant senderParticipant = session.getParticipantByPublicId(senderName);
+
+ // Modify sdp if transcoding is not allowed
+ if (!session.getSessionProperties().isTranscodingAllowed()) {
+ VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
+ try {
+ sdpAnswer = this.modifySdpToForceCodec(forcedVideoCodec, sdpAnswer);
+ } catch (OpenViduException e) {
+ String errorMessage = "Error forcing codec: ''" + forcedVideoCodec + "', for subscriber on Session: '"
+ + session.getSessionId() + "'\nException: " + e.getMessage() + "\nSDP:\n" + sdpAnswer;
+ throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, errorMessage);
+ }
+ }
if (senderParticipant == null) {
log.warn(
@@ -1086,10 +1117,26 @@ public class KurentoSessionManager extends SessionManager {
@Override
public void reconnectStream(Participant participant, String streamId, String sdpString, Integer transactionId) {
+
KurentoParticipant kParticipant = (KurentoParticipant) participant;
KurentoSession kSession = kParticipant.getSession();
+ boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
+
+ // Modify sdp if transcoding is not allowed
+ if (!kSession.getSessionProperties().isTranscodingAllowed()) {
+ VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
+ try {
+ sdpString = modifySdpToForceCodec(forcedVideoCodec, sdpString);
+ } catch (OpenViduException e) {
+ String errorMessage = "Error on reconnecting and forcing codec: ''" + forcedVideoCodec + "', for "
+ + (isPublisher ? "publisher" : "subscriber") + " on Session: '" + kSession.getSessionId()
+ + "'\nException: " + e.getMessage() + "\nSDP:\n" + sdpString;
+ throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, errorMessage);
+ }
+
+ }
- if (streamId.equals(participant.getPublisherStreamId())) {
+ if (isPublisher) {
// Reconnect publisher
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
@@ -1192,5 +1239,14 @@ public class KurentoSessionManager extends SessionManager {
filter.removeEventListener(pub.removeListener(eventType));
}
}
-
+
+
+ private String modifySdpToForceCodec(VideoCodec codec, String sdpOffer) {
+ // Modify sdpOffer if transcoding is not allowed
+ String modSdpOffer = this.sdpMunging.setCodecPreference(codec, sdpOffer);
+ if (modSdpOffer != null) {
+ sdpOffer = modSdpOffer;
+ }
+ return sdpOffer;
+ }
}
diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
index 28da7b54..e773c314 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java
@@ -24,6 +24,12 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,12 +46,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
import io.openvidu.client.OpenViduException;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.MediaMode;
@@ -55,6 +55,7 @@ import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingMode;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.SessionProperties;
+import io.openvidu.java.client.VideoCodec;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.IdentifierPrefixes;
@@ -102,6 +103,8 @@ public class SessionRestController {
String defaultOutputModeString;
String defaultRecordingLayoutString;
String defaultCustomLayout;
+ String forcedVideoCodec;
+ Boolean allowTranscoding;
try {
mediaModeString = (String) params.get("mediaMode");
recordingModeString = (String) params.get("recordingMode");
@@ -109,6 +112,8 @@ public class SessionRestController {
defaultRecordingLayoutString = (String) params.get("defaultRecordingLayout");
defaultCustomLayout = (String) params.get("defaultCustomLayout");
customSessionId = (String) params.get("customSessionId");
+ forcedVideoCodec = (String) params.get("forcedVideoCodec");
+ allowTranscoding = (Boolean) params.get("allowTranscoding");
} catch (ClassCastException e) {
return this.generateErrorResponse("Type error in some parameter", "/api/sessions",
HttpStatus.BAD_REQUEST);
@@ -150,6 +155,16 @@ public class SessionRestController {
builder = builder.customSessionId(customSessionId);
}
builder = builder.defaultCustomLayout((defaultCustomLayout != null) ? defaultCustomLayout : "");
+ if (forcedVideoCodec != null) {
+ builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
+ } else {
+ builder = builder.forcedVideoCodec(VideoCodec.VP8);
+ }
+ if (allowTranscoding != null) {
+ builder = builder.allowTranscoding(allowTranscoding);
+ } else {
+ builder = builder.allowTranscoding(false);
+ }
} catch (IllegalArgumentException e) {
return this.generateErrorResponse("RecordingMode " + params.get("recordingMode") + " | "
diff --git a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
index 4e742517..f9cc6f00 100644
--- a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
+++ b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java
@@ -26,6 +26,11 @@ import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpSession;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
import org.apache.commons.lang3.RandomStringUtils;
import org.kurento.jsonrpc.DefaultJsonRpcHandler;
import org.kurento.jsonrpc.Session;
@@ -37,11 +42,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
-
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
diff --git a/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java
new file mode 100644
index 00000000..8537c39e
--- /dev/null
+++ b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java
@@ -0,0 +1,133 @@
+package io.openvidu.server.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.openvidu.client.OpenViduException;
+import io.openvidu.client.OpenViduException.Code;
+import io.openvidu.java.client.VideoCodec;
+
+public class SDPMunging {
+
+ private static final Logger log = LoggerFactory.getLogger(SDPMunging.class);
+
+ /**
+ * `codec` is a uppercase SDP-style codec name: "VP8", "H264".
+ *
+ * This looks for all video m-sections (lines starting with "m=video"),
+ * then searches all of its related PayloadTypes trying to find those which
+ * correspond to the preferred codec. If any is found, they are moved to the
+ * front of the PayloadTypes list in the m= line, without removing the other
+ * codecs that might be present.
+ *
+ * If our preferred codec is not found, the m= line is left without changes.
+ *
+ * This works based on the basis that RFC 3264 "Offer/Answer Model SDP" section
+ * 6.1 "Unicast Streams" allows the answerer to list media formats in a
+ * different order of preference from what it got in the offer:
+ *
+ * > Although the answerer MAY list the formats in their desired order of
+ * > preference, it is RECOMMENDED that unless there is a specific reason,
+ * > the answerer list formats in the same relative order they were
+ * > present in the offer.
+ *
+ * Here we have a specific reason, thus we use this allowance to change the
+ * ordering of formats. Browsers (tested with Chrome 84) honor this change and
+ * use the first codec provided in the answer, so this operation actually works.
+ */
+ public String setCodecPreference(VideoCodec codec, String sdp) throws OpenViduException {
+ String codecStr = codec.name();
+ log.info("[setCodecPreference] codec: {}", codecStr);
+
+ List codecPts = new ArrayList();
+ String[] lines = sdp.split("\\R+");
+ Pattern ptRegex = Pattern.compile(String.format("a=rtpmap:(\\d+) %s/90000", codecStr));
+
+ for (int sl = 0; sl < lines.length; sl++) {
+ String sdpLine = lines[sl];
+
+ if (!sdpLine.startsWith("m=video")) {
+ continue;
+ }
+
+ // m-section found. Prepare an array to store PayloadTypes.
+ codecPts.clear();
+
+ // Search the m-section to find our codec's PayloadType, if any.
+ for (int ml = sl + 1; ml < lines.length; ml++) {
+ String mediaLine = lines[ml];
+
+ // Abort if we reach the next m-section.
+ if (mediaLine.startsWith("m=")) {
+ break;
+ }
+
+ Matcher ptMatch = ptRegex.matcher(mediaLine);
+ if (ptMatch.find()) {
+ // PayloadType found.
+ String pt = ptMatch.group(1);
+ codecPts.add(pt);
+
+ // Search the m-section to find the APT subtype, if any.
+ Pattern aptRegex = Pattern.compile(String.format("a=fmtp:(\\d+) apt=%s", pt));
+
+ for (int al = sl + 1; al < lines.length; al++) {
+ String aptLine = lines[al];
+
+ // Abort if we reach the next m-section.
+ if (aptLine.startsWith("m=")) {
+ break;
+ }
+
+ Matcher aptMatch = aptRegex.matcher(aptLine);
+ if (aptMatch.find()) {
+ // APT found.
+ String apt = aptMatch.group(1);
+ codecPts.add(apt);
+ }
+ }
+ }
+ }
+
+ if (codecPts.isEmpty()) {
+ throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, "The specified forced codec " + codecStr + " is not present in the SDP");
+ }
+
+ // Build a new m= line where any PayloadTypes found have been moved
+ // to the front of the PT list.
+ StringBuilder newLine = new StringBuilder(sdpLine.length());
+ List lineParts = new ArrayList(Arrays.asList(sdpLine.split(" ")));
+
+ if (lineParts.size() < 4) {
+ log.error("[setCodecPreference] BUG in m= line: Expects at least 4 fields: '{}'", sdpLine);
+ continue;
+ }
+
+ // Add "m=video", Port, and Protocol.
+ for (int i = 0; i < 3; i++) {
+ newLine.append(lineParts.remove(0) + " ");
+ }
+
+ // Add the PayloadTypes that correspond to our preferred codec.
+ for (String pt : codecPts) {
+ lineParts.remove(pt);
+ newLine.append(pt + " ");
+ }
+
+ // Add the rest of PayloadTypes.
+ newLine.append(String.join(" ", lineParts));
+
+ // Replace the original m= line with the one we just built.
+ lines[sl] = newLine.toString();
+ }
+
+ return String.join("\r\n", lines);
+ }
+
+}
\ No newline at end of file
diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css
index 5c4c9594..782565c8 100644
--- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css
+++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.css
@@ -36,4 +36,8 @@ mat-radio-button:first-child {
#role-div {
padding-top: 6px;
padding-bottom: 15px;
+}
+
+#allow-transcoding-div {
+ margin-bottom: 10px;
}
\ No newline at end of file
diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html
index de5608d5..bf816c9b 100644
--- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html
+++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html
@@ -38,6 +38,18 @@
+
+ Allow Transcoding
+
+
+
+
+ {{ enumerator }}
+
+
+
diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.ts b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.ts
index b7427ed2..f06f3201 100644
--- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.ts
+++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.ts
@@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
-import { SessionProperties, MediaMode, Recording, RecordingMode, RecordingLayout, TokenOptions } from 'openvidu-node-client';
+import { SessionProperties, MediaMode, Recording, RecordingMode, RecordingLayout, TokenOptions, VideoCodec } from 'openvidu-node-client';
@Component({
selector: 'app-session-properties-dialog',
@@ -23,6 +23,7 @@ export class SessionPropertiesDialogComponent {
recordingMode = RecordingMode;
defaultOutputMode = Recording.OutputMode;
defaultRecordingLayout = RecordingLayout;
+ forceVideoCodec = VideoCodec;
constructor(public dialogRef: MatDialogRef,
@Inject(MAT_DIALOG_DATA) public data) {
diff --git a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts
index eda242a8..fc324f9f 100644
--- a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts
+++ b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts
@@ -18,7 +18,8 @@ import {
TokenOptions,
OpenViduRole,
RecordingProperties,
- Recording
+ Recording,
+ VideoCodec
} from 'openvidu-node-client';
import { MatDialog, MAT_CHECKBOX_CLICK_ACTION } from '@angular/material';
import { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component';
@@ -92,7 +93,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
defaultOutputMode: Recording.OutputMode.COMPOSED,
defaultRecordingLayout: RecordingLayout.BEST_FIT,
defaultCustomLayout: '',
- customSessionId: ''
+ customSessionId: '',
+ forcedVideoCodec: VideoCodec.VP8,
+ allowTranscoding: false
};
publisherProperties: PublisherProperties = {