mirror of https://github.com/OpenVidu/openvidu.git
Force codec to avoid transcoding working.
parent
3086b4aaf2
commit
42a2fed910
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,6 +143,34 @@ public class SessionProperties {
|
|||
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 <code>false</code>.
|
||||
*
|
||||
*/
|
||||
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 <code>false</code>, 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 <code>true</code>, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected SessionProperties() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -231,4 +269,24 @@ public class SessionProperties {
|
|||
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 <code>false</code>
|
||||
*/
|
||||
public VideoCodec forcedVideoCodec() {
|
||||
return this.forcedVideoCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Defines if transcoding is allowed or not. If this method returns <code>false</code>, 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 <code>false</code>.
|
||||
*/
|
||||
forcedVideoCodec?: string;
|
||||
|
||||
/**
|
||||
* Call this method to define if you want to allowTranscoding or not. If you define it as
|
||||
* as <code>false</code>, 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 <code>true</code>, 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;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* See [[SessionProperties.forcedVideoCodec]]
|
||||
*/
|
||||
export enum VideoCodec {
|
||||
|
||||
VP8 = 'VP8',
|
||||
VP9 = 'VP9',
|
||||
H264 = 'H264'
|
||||
|
||||
}
|
|
@ -10,3 +10,4 @@ export * from './Recording';
|
|||
export * from './RecordingProperties';
|
||||
export * from './Connection';
|
||||
export * from './Publisher';
|
||||
export * from './VideoCodec';
|
|
@ -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
|
||||
# -------------------------------
|
||||
|
|
|
@ -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
|
||||
# -------------------------------
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -196,6 +197,12 @@ public class OpenViduServer implements JsonRpcConfigurer {
|
|||
return new GeoLocationByIpDummy();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SDPMunging sdpMunging() {
|
||||
return new SDPMunging();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public QuarantineKiller quarantineKiller() {
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
@ -82,6 +84,9 @@ public class KurentoSessionManager extends SessionManager {
|
|||
@Autowired
|
||||
private KurentoParticipantEndpointConfig kurentoEndpointConfig;
|
||||
|
||||
@Autowired
|
||||
private SDPMunging sdpMunging;
|
||||
|
||||
@Override
|
||||
/* Protected by Session.closingLock.readLock */
|
||||
public void joinRoom(Participant participant, String sessionId, Integer transactionId) {
|
||||
|
@ -365,6 +370,22 @@ 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={} "
|
||||
|
@ -372,8 +393,6 @@ public class KurentoSessionManager extends SessionManager {
|
|||
kurentoOptions.isOffer, kurentoOptions.sdpOffer, kurentoOptions.doLoopback, kurentoOptions.rtspUri,
|
||||
participant.getParticipantPublicId());
|
||||
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
|
||||
kParticipant.createPublishingEndpoint(mediaOptions, null);
|
||||
|
||||
/*
|
||||
|
@ -559,6 +578,18 @@ public class KurentoSessionManager extends SessionManager {
|
|||
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(
|
||||
"PARTICIPANT {}: Requesting to recv media from user {} "
|
||||
|
@ -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());
|
||||
|
||||
if (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 (isPublisher) {
|
||||
|
||||
// Reconnect publisher
|
||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
||||
|
@ -1193,4 +1240,13 @@ public class KurentoSessionManager extends SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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") + " | "
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String> codecPts = new ArrayList<String>();
|
||||
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<String> lineParts = new ArrayList<String>(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -37,3 +37,7 @@ mat-radio-button:first-child {
|
|||
padding-top: 6px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
#allow-transcoding-div {
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -38,6 +38,18 @@
|
|||
<input matInput placeholder="DefaultCustomLayout" type="text"
|
||||
[(ngModel)]="sessionProperties.defaultCustomLayout">
|
||||
</mat-form-field>
|
||||
<div id="allow-transcoding-div">
|
||||
<mat-checkbox class="checkbox-form" [(ngModel)]="sessionProperties.allowTranscoding"
|
||||
id="allow-transcoding-checkbox">Allow Transcoding</mat-checkbox>
|
||||
</div>
|
||||
<mat-form-field *ngIf="!sessionProperties.allowTranscoding">
|
||||
<mat-select placeholder="ForcedVideoCodec" [(ngModel)]="sessionProperties.forcedVideoCodec"
|
||||
id="forced-video-codec-select">
|
||||
<mat-option *ngFor="let enumerator of enumToArray(forceVideoCodec)" [value]="enumerator">
|
||||
<span [attr.id]="'option-' + enumerator">{{ enumerator }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="CustomSessionId" type="text" [(ngModel)]="sessionProperties.customSessionId">
|
||||
</mat-form-field>
|
||||
|
|
|
@ -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<SessionPropertiesDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data) {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue