mirror of https://github.com/OpenVidu/openvidu.git
Force codec parameters to avoid transcoding
parent
f09b5c97f3
commit
7a25233b8b
|
@ -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;
|
||||
|
||||
|
|
|
@ -56,14 +56,14 @@ public class Session {
|
|||
protected Session(OpenVidu openVidu) throws OpenViduJavaClientException, OpenViduHttpException {
|
||||
this.openVidu = openVidu;
|
||||
this.properties = new SessionProperties.Builder().build();
|
||||
this.getSessionIdHttp();
|
||||
this.getSessionHttp();
|
||||
}
|
||||
|
||||
protected Session(OpenVidu openVidu, SessionProperties properties)
|
||||
throws OpenViduJavaClientException, OpenViduHttpException {
|
||||
this.openVidu = openVidu;
|
||||
this.properties = properties;
|
||||
this.getSessionIdHttp();
|
||||
this.getSessionHttp();
|
||||
}
|
||||
|
||||
protected Session(OpenVidu openVidu, JsonObject json) {
|
||||
|
@ -655,7 +655,7 @@ public class Session {
|
|||
return (this.sessionId != null && !this.sessionId.isEmpty());
|
||||
}
|
||||
|
||||
private void getSessionIdHttp() throws OpenViduJavaClientException, OpenViduHttpException {
|
||||
private void getSessionHttp() throws OpenViduJavaClientException, OpenViduHttpException {
|
||||
if (this.hasSessionId()) {
|
||||
return;
|
||||
}
|
||||
|
@ -683,6 +683,25 @@ public class Session {
|
|||
JsonObject responseJson = httpResponseToJson(response);
|
||||
this.sessionId = responseJson.get("id").getAsString();
|
||||
this.createdAt = responseJson.get("createdAt").getAsLong();
|
||||
|
||||
// forcedVideoCodec and allowTranscoding values are configured in OpenVidu Server
|
||||
// via configuration or session
|
||||
VideoCodec forcedVideoCodec = VideoCodec.valueOf(responseJson.get("forcedVideoCodec").getAsString());
|
||||
Boolean allowTranscoding = responseJson.get("allowTranscoding").getAsBoolean();
|
||||
|
||||
SessionProperties responseProperties = new SessionProperties.Builder()
|
||||
.customSessionId(properties.customSessionId())
|
||||
.mediaMode(properties.mediaMode())
|
||||
.recordingMode(properties.recordingMode())
|
||||
.defaultOutputMode(properties.defaultOutputMode())
|
||||
.defaultRecordingLayout(properties.defaultRecordingLayout())
|
||||
.defaultCustomLayout(properties.defaultCustomLayout())
|
||||
.mediaNode(properties.mediaNode())
|
||||
.forcedVideoCodec(forcedVideoCodec)
|
||||
.allowTranscoding(allowTranscoding)
|
||||
.build();
|
||||
|
||||
this.properties = responseProperties;
|
||||
log.info("Session '{}' created", this.sessionId);
|
||||
} else if (statusCode == org.apache.http.HttpStatus.SC_CONFLICT) {
|
||||
// 'customSessionId' already existed
|
||||
|
@ -727,6 +746,13 @@ public class Session {
|
|||
builder.customSessionId(json.get("customSessionId").getAsString());
|
||||
}
|
||||
|
||||
if (json.has("forcedVideoCodec")) {
|
||||
builder.forcedVideoCodec(VideoCodec.valueOf(json.get("forcedVideoCodec").getAsString()));
|
||||
}
|
||||
if (json.has("allowTranscoding")) {
|
||||
builder.allowTranscoding(json.get("allowTranscoding").getAsBoolean());
|
||||
}
|
||||
|
||||
this.properties = builder.build();
|
||||
JsonArray jsonArrayConnections = (json.get("connections").getAsJsonObject()).get("content").getAsJsonArray();
|
||||
|
||||
|
@ -768,6 +794,12 @@ public class Session {
|
|||
json.addProperty("defaultOutputMode", this.properties.defaultOutputMode().name());
|
||||
json.addProperty("defaultRecordingLayout", this.properties.defaultRecordingLayout().name());
|
||||
json.addProperty("defaultCustomLayout", this.properties.defaultCustomLayout());
|
||||
if(this.properties.forcedVideoCodec() != null) {
|
||||
json.addProperty("forcedVideoCodec", this.properties.forcedVideoCodec().name());
|
||||
}
|
||||
if (this.properties.isTranscodingAllowed() != null) {
|
||||
json.addProperty("allowTranscoding", this.properties.isTranscodingAllowed());
|
||||
}
|
||||
JsonObject connections = new JsonObject();
|
||||
connections.addProperty("numberOfElements", this.getConnections().size());
|
||||
JsonArray jsonArrayConnections = new JsonArray();
|
||||
|
|
|
@ -33,6 +33,8 @@ public class SessionProperties {
|
|||
private String defaultCustomLayout;
|
||||
private String customSessionId;
|
||||
private String mediaNode;
|
||||
private VideoCodec forcedVideoCodec;
|
||||
private Boolean allowTranscoding;
|
||||
|
||||
/**
|
||||
* Builder for {@link io.openvidu.java.client.SessionProperties}
|
||||
|
@ -46,6 +48,8 @@ public class SessionProperties {
|
|||
private String defaultCustomLayout = "";
|
||||
private String customSessionId = "";
|
||||
private String mediaNode;
|
||||
private VideoCodec forcedVideoCodec;
|
||||
private Boolean allowTranscoding;
|
||||
|
||||
/**
|
||||
* Returns the {@link io.openvidu.java.client.SessionProperties} object properly
|
||||
|
@ -53,7 +57,8 @@ public class SessionProperties {
|
|||
*/
|
||||
public SessionProperties build() {
|
||||
return new SessionProperties(this.mediaMode, this.recordingMode, this.defaultOutputMode,
|
||||
this.defaultRecordingLayout, this.defaultCustomLayout, this.customSessionId, this.mediaNode);
|
||||
this.defaultRecordingLayout, this.defaultCustomLayout, this.customSessionId, this.mediaNode,
|
||||
this.forcedVideoCodec, this.allowTranscoding);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,6 +160,28 @@ 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/clients to use the same codec avoiding transcoding in the media server.
|
||||
* If the browser/client is not compatible with the specified codec and {@link #allowTranscoding(Boolean)}
|
||||
* is <code>false</code> and exception will occur.
|
||||
*
|
||||
* If forcedVideoCodec is set to NONE, no codec will be forced.
|
||||
*/
|
||||
public SessionProperties.Builder forcedVideoCodec(VideoCodec forcedVideoCodec) {
|
||||
this.forcedVideoCodec = forcedVideoCodec;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to define if you want to allow transcoding in the media server or not
|
||||
* when {@link #forcedVideoCodec(VideoCodec)} is not compatible with the browser/client.
|
||||
*/
|
||||
public SessionProperties.Builder allowTranscoding(Boolean allowTranscoding) {
|
||||
this.allowTranscoding = allowTranscoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected SessionProperties() {
|
||||
|
@ -168,7 +195,8 @@ public class SessionProperties {
|
|||
}
|
||||
|
||||
private SessionProperties(MediaMode mediaMode, RecordingMode recordingMode, OutputMode outputMode,
|
||||
RecordingLayout layout, String defaultCustomLayout, String customSessionId, String mediaNode) {
|
||||
RecordingLayout layout, String defaultCustomLayout, String customSessionId, String mediaNode,
|
||||
VideoCodec forcedVideoCodec, Boolean allowTranscoding) {
|
||||
this.mediaMode = mediaMode;
|
||||
this.recordingMode = recordingMode;
|
||||
this.defaultOutputMode = outputMode;
|
||||
|
@ -176,6 +204,8 @@ public class SessionProperties {
|
|||
this.defaultCustomLayout = defaultCustomLayout;
|
||||
this.customSessionId = customSessionId;
|
||||
this.mediaNode = mediaNode;
|
||||
this.forcedVideoCodec = forcedVideoCodec;
|
||||
this.allowTranscoding = allowTranscoding;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,6 +293,21 @@ public class SessionProperties {
|
|||
return this.mediaNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which video codec is being forced to be used in the browser/client
|
||||
*/
|
||||
public VideoCodec forcedVideoCodec() {
|
||||
return this.forcedVideoCodec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if transcoding is allowed or not when {@link #forcedVideoCodec}
|
||||
* is not a compatible codec with the browser/client.
|
||||
*/
|
||||
public Boolean isTranscodingAllowed() {
|
||||
return this.allowTranscoding;
|
||||
}
|
||||
|
||||
protected JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("mediaMode", mediaMode().name());
|
||||
|
@ -276,6 +321,12 @@ public class SessionProperties {
|
|||
mediaNodeJson.addProperty("id", mediaNode());
|
||||
json.add("mediaNode", mediaNodeJson);
|
||||
}
|
||||
if (forcedVideoCodec() != null) {
|
||||
json.addProperty("forcedVideoCodec", forcedVideoCodec().name());
|
||||
}
|
||||
if (isTranscodingAllowed() != null) {
|
||||
json.addProperty("allowTranscoding", isTranscodingAllowed());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
|
|
|
@ -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, NONE
|
||||
}
|
|
@ -106,8 +106,8 @@ export class OpenVidu {
|
|||
public createSession(properties?: SessionProperties): Promise<Session> {
|
||||
return new Promise<Session>((resolve, reject) => {
|
||||
const session = new Session(this, properties);
|
||||
session.getSessionIdHttp()
|
||||
.then(sessionId => {
|
||||
session.getSessionHttp()
|
||||
.then(response => {
|
||||
this.activeSessions.push(session);
|
||||
resolve(session);
|
||||
})
|
||||
|
|
|
@ -94,6 +94,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.forcedVideoCodec = !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : undefined;
|
||||
this.properties.allowTranscoding = this.properties.allowTranscoding != null ? this.properties.allowTranscoding : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -452,22 +454,25 @@ export class Session {
|
|||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public getSessionIdHttp(): Promise<string> {
|
||||
public getSessionHttp(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
|
||||
if (!!this.sessionId) {
|
||||
resolve(this.sessionId);
|
||||
}
|
||||
|
||||
const data = JSON.stringify({
|
||||
mediaMode: !!this.properties.mediaMode ? this.properties.mediaMode : MediaMode.ROUTED,
|
||||
recordingMode: !!this.properties.recordingMode ? this.properties.recordingMode : RecordingMode.MANUAL,
|
||||
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 : '',
|
||||
mediaNode: !!this.properties.mediaNode ? this.properties.mediaNode : undefined
|
||||
});
|
||||
const mediaMode = !!this.properties.mediaMode ? this.properties.mediaMode : MediaMode.ROUTED;
|
||||
const recordingMode = !!this.properties.recordingMode ? this.properties.recordingMode : RecordingMode.MANUAL;
|
||||
const defaultOutputMode = !!this.properties.defaultOutputMode ? this.properties.defaultOutputMode : Recording.OutputMode.COMPOSED;
|
||||
const defaultRecordingLayout = !!this.properties.defaultRecordingLayout ? this.properties.defaultRecordingLayout : RecordingLayout.BEST_FIT;
|
||||
const defaultCustomLayout = !!this.properties.defaultCustomLayout ? this.properties.defaultCustomLayout : '';
|
||||
const customSessionId = !!this.properties.customSessionId ? this.properties.customSessionId : '';
|
||||
const mediaNode = !!this.properties.mediaNode ? this.properties.mediaNode : undefined;
|
||||
const forcedVideoCodec = !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : undefined;
|
||||
const allowTranscoding = this.properties.allowTranscoding != null ? this.properties.allowTranscoding : undefined;
|
||||
|
||||
const data = JSON.stringify({mediaMode, recordingMode, defaultOutputMode, defaultRecordingLayout, defaultCustomLayout,
|
||||
customSessionId, mediaNode, forcedVideoCodec, allowTranscoding});
|
||||
|
||||
axios.post(
|
||||
this.ov.host + OpenVidu.API_SESSIONS,
|
||||
|
@ -484,6 +489,15 @@ export class Session {
|
|||
// SUCCESS response from openvidu-server. Resolve token
|
||||
this.sessionId = res.data.id;
|
||||
this.createdAt = res.data.createdAt;
|
||||
this.properties.mediaMode = mediaMode;
|
||||
this.properties.recordingMode = recordingMode;
|
||||
this.properties.defaultOutputMode = defaultOutputMode;
|
||||
this.properties.defaultRecordingLayout = defaultRecordingLayout;
|
||||
this.properties.defaultCustomLayout = defaultCustomLayout;
|
||||
this.properties.customSessionId = customSessionId;
|
||||
this.properties.mediaNode = mediaNode;
|
||||
this.properties.forcedVideoCodec = res.data.forcedVideoCodec;
|
||||
this.properties.allowTranscoding = res.data.allowTranscoding;
|
||||
resolve(this.sessionId);
|
||||
} else {
|
||||
// ERROR response from openvidu-server. Resolve HTTP status
|
||||
|
@ -527,7 +541,9 @@ export class Session {
|
|||
recordingMode: json.recordingMode,
|
||||
defaultOutputMode: json.defaultOutputMode,
|
||||
defaultRecordingLayout: json.defaultRecordingLayout,
|
||||
defaultCustomLayout: json.defaultCustomLayout
|
||||
defaultCustomLayout: json.defaultCustomLayout,
|
||||
forcedVideoCodec: json.forcedVideoCodec,
|
||||
allowTranscoding: json.allowTranscoding
|
||||
};
|
||||
if (json.defaultRecordingLayout == null) {
|
||||
delete this.properties.defaultRecordingLayout;
|
||||
|
@ -538,6 +554,15 @@ export class Session {
|
|||
if (json.defaultCustomLayout == null) {
|
||||
delete this.properties.defaultCustomLayout;
|
||||
}
|
||||
if (json.mediaNode == null) {
|
||||
delete this.properties.mediaNode;
|
||||
}
|
||||
if (json.forcedVideoCodec == null) {
|
||||
delete this.properties.forcedVideoCodec;
|
||||
}
|
||||
if (json.allowTranscoding == null) {
|
||||
delete this.properties.allowTranscoding;
|
||||
}
|
||||
|
||||
// 1. Array to store fetched connections and later remove closed ones
|
||||
const fetchedConnectionIds: string[] = [];
|
||||
|
@ -567,7 +592,6 @@ export class Session {
|
|||
this.connections.sort((c1, c2) => (c1.createdAt > c2.createdAt) ? 1 : ((c2.createdAt > c1.createdAt) ? -1 : 0));
|
||||
// Populate activeConnections array
|
||||
this.updateActiveConnectionsArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -76,4 +76,20 @@ export interface SessionProperties {
|
|||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* It defines which video codec do you want to be forcibly used for this session.
|
||||
* This allows browsers/clients to use the same codec avoiding transcoding in the media server.
|
||||
* If the browser/client is not compatible with the specified codec and [[allowTranscoding]]
|
||||
* is <code>false</code> and exception will occur.
|
||||
*
|
||||
* If forcedVideoCodec is set to NONE, no codec will be forced.
|
||||
*/
|
||||
forcedVideoCodec?: string;
|
||||
|
||||
/**
|
||||
* It defines if you want to allow transcoding in the media server or not
|
||||
* when [[forcedVideoCodec]] is not compatible with the browser/client.
|
||||
*/
|
||||
allowTranscoding?: boolean;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* See [[SessionProperties.forcedVideoCodec]]
|
||||
*/
|
||||
export enum VideoCodec {
|
||||
|
||||
VP8 = 'VP8',
|
||||
VP9 = 'VP9',
|
||||
H264 = 'H264',
|
||||
NONE = 'NONE'
|
||||
|
||||
}
|
|
@ -12,3 +12,4 @@ export * from './Recording';
|
|||
export * from './RecordingProperties';
|
||||
export * from './Connection';
|
||||
export * from './Publisher';
|
||||
export * from './VideoCodec';
|
|
@ -153,6 +153,15 @@ OPENVIDU_SESSIONS_GARBAGE_INTERVAL=900
|
|||
# (property 'OPENVIDU_SESSIONS_GARBAGE_INTERVAL' to 0) this property is ignored
|
||||
OPENVIDU_SESSIONS_GARBAGE_THRESHOLD=3600
|
||||
|
||||
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_ALLOW_TRANSCODING=true
|
||||
# when a codec can not be forced, transcoding will be allowed
|
||||
# Default value is VP8
|
||||
# OPENVIDU_FORCED_CODEC=VP8
|
||||
|
||||
# Allow transcoding if codec specified in OPENVIDU_FORCED_CODEC can not be applied
|
||||
# Default value is false
|
||||
# OPENVIDU_ALLOW_TRANSCODING=false
|
||||
|
||||
# Call Detail Record enabled
|
||||
# Whether to enable Call Detail Record or not
|
||||
# Values: true | false
|
||||
|
|
|
@ -259,6 +259,15 @@ OPENVIDU_SESSIONS_GARBAGE_INTERVAL=900
|
|||
# (property 'OPENVIDU_SESSIONS_GARBAGE_INTERVAL' to 0) this property is ignored
|
||||
OPENVIDU_SESSIONS_GARBAGE_THRESHOLD=3600
|
||||
|
||||
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_ALLOW_TRANSCODING=true
|
||||
# when a codec can not be forced, transcoding will be allowed
|
||||
# Default value is VP8
|
||||
# OPENVIDU_FORCED_CODEC=VP8
|
||||
|
||||
# Allow transcoding if codec specified in OPENVIDU_FORCED_CODEC can not be applied
|
||||
# Default value is false
|
||||
# OPENVIDU_ALLOW_TRANSCODING=false
|
||||
|
||||
# Call Detail Record enabled
|
||||
# Whether to enable Call Detail Record or not
|
||||
# Values: true | false
|
||||
|
|
|
@ -79,6 +79,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;
|
||||
|
||||
/**
|
||||
|
@ -216,6 +217,12 @@ public class OpenViduServer implements JsonRpcConfigurer {
|
|||
return new GeoLocationByIpDummy();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SDPMunging sdpMunging() {
|
||||
return new SDPMunging();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public QuarantineKiller quarantineKiller() {
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.util.Map;
|
|||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.Header;
|
||||
|
@ -180,6 +181,10 @@ public class OpenviduConfig {
|
|||
|
||||
protected int openviduSessionsGarbageThreshold;
|
||||
|
||||
private VideoCodec openviduForcedCodec;
|
||||
|
||||
private boolean openviduAllowTranscoding;
|
||||
|
||||
private String dotenvPath;
|
||||
|
||||
// Derived properties
|
||||
|
@ -314,6 +319,14 @@ public class OpenviduConfig {
|
|||
return openviduSessionsGarbageThreshold;
|
||||
}
|
||||
|
||||
public VideoCodec getOpenviduForcedCodec() {
|
||||
return openviduForcedCodec;
|
||||
}
|
||||
|
||||
public boolean isOpenviduAllowingTranscoding() {
|
||||
return openviduAllowTranscoding;
|
||||
}
|
||||
|
||||
public String getDotenvPath() {
|
||||
return dotenvPath;
|
||||
}
|
||||
|
@ -510,6 +523,9 @@ public class OpenviduConfig {
|
|||
openviduSessionsGarbageInterval = asNonNegativeInteger("OPENVIDU_SESSIONS_GARBAGE_INTERVAL");
|
||||
openviduSessionsGarbageThreshold = asNonNegativeInteger("OPENVIDU_SESSIONS_GARBAGE_THRESHOLD");
|
||||
|
||||
openviduForcedCodec = asEnumValue("OPENVIDU_FORCED_CODEC", VideoCodec.class);
|
||||
openviduAllowTranscoding = asBoolean("OPENVIDU_ALLOW_TRANSCODING");
|
||||
|
||||
kmsUrisList = checkKmsUris();
|
||||
|
||||
checkCoturnIp();
|
||||
|
|
|
@ -210,7 +210,7 @@ public class Participant {
|
|||
}
|
||||
|
||||
public boolean isIpcam() {
|
||||
return this.platform.equals("IPCAM") && this.participantPrivatetId.startsWith(IdentifierPrefixes.IPCAM_ID);
|
||||
return this.platform != null && this.platform.equals("IPCAM") && this.participantPrivatetId.startsWith(IdentifierPrefixes.IPCAM_ID);
|
||||
}
|
||||
|
||||
public String getPublisherStreamId() {
|
||||
|
|
|
@ -256,6 +256,8 @@ public class Session implements SessionInterface {
|
|||
connections.add("content", participants);
|
||||
json.add("connections", connections);
|
||||
json.addProperty("recording", this.recordingManager.sessionIsBeingRecorded(this.sessionId));
|
||||
json.addProperty("forcedVideoCodec", this.sessionProperties.forcedVideoCodec().name());
|
||||
json.addProperty("allowTranscoding", this.sessionProperties.isTranscodingAllowed());
|
||||
return json;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.openvidu.server.utils.SDPMunging;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.kurento.client.GenericMediaElement;
|
||||
import org.kurento.client.IceCandidate;
|
||||
|
@ -56,6 +57,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;
|
||||
|
@ -86,6 +88,9 @@ public class KurentoSessionManager extends SessionManager {
|
|||
@Autowired
|
||||
protected KurentoParticipantEndpointConfig kurentoEndpointConfig;
|
||||
|
||||
@Autowired
|
||||
private SDPMunging sdpMunging;
|
||||
|
||||
@Override
|
||||
/* Protected by Session.closingLock.readLock */
|
||||
public void joinRoom(Participant participant, String sessionId, Integer transactionId) {
|
||||
|
@ -345,7 +350,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
* generated by the WebRTC endpoint on the server.
|
||||
*
|
||||
* @param participant Participant publishing video
|
||||
* @param MediaOptions configuration of the stream to publish
|
||||
* @param mediaOptions configuration of the stream to publish
|
||||
* @param transactionId identifier of the Transaction
|
||||
* @throws OpenViduException on error
|
||||
*/
|
||||
|
@ -358,16 +363,22 @@ public class KurentoSessionManager extends SessionManager {
|
|||
|
||||
KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) mediaOptions;
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
||||
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
||||
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
kurentoOptions.sdpOffer = sdpMunging.forceCodec(participant, kurentoOptions.sdpOffer, kurentoOptions.isOffer,
|
||||
kSession, true, false, isTranscodingAllowed, forcedVideoCodec);
|
||||
}
|
||||
log.debug(
|
||||
"Request [PUBLISH_MEDIA] isOffer={} sdp={} "
|
||||
+ "loopbackAltSrc={} lpbkConnType={} doLoopback={} rtspUri={} ({})",
|
||||
kurentoOptions.isOffer, kurentoOptions.sdpOffer, kurentoOptions.doLoopback, kurentoOptions.rtspUri,
|
||||
participant.getParticipantPublicId());
|
||||
|
||||
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
|
||||
kParticipant.createPublishingEndpoint(mediaOptions, null);
|
||||
|
||||
/*
|
||||
|
@ -462,6 +473,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
participants = kParticipant.getSession().getParticipants();
|
||||
|
||||
if (sdpAnswer != null) {
|
||||
log.debug("SDP Answer for publishing PARTICIPANT {}: {}", participant.getParticipantPublicId(), sdpAnswer);
|
||||
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), mediaOptions, sdpAnswer,
|
||||
participants, transactionId, null);
|
||||
|
@ -510,6 +522,14 @@ public class KurentoSessionManager extends SessionManager {
|
|||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
session = ((KurentoParticipant) participant).getSession();
|
||||
Participant senderParticipant = session.getParticipantByPublicId(senderName);
|
||||
boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
|
||||
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpOffer = sdpMunging.forceCodec(participant, sdpOffer, true, session, false, false,
|
||||
isTranscodingAllowed, forcedVideoCodec);
|
||||
}
|
||||
|
||||
if (senderParticipant == null) {
|
||||
log.warn(
|
||||
|
@ -539,6 +559,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
sessionEventsHandler.onSubscribe(participant, session, null, transactionId, e);
|
||||
}
|
||||
if (sdpAnswer != null) {
|
||||
log.debug("SDP Answer for subscribing PARTICIPANT {}: {}", participant.getParticipantPublicId(), sdpAnswer);
|
||||
sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null);
|
||||
}
|
||||
}
|
||||
|
@ -1049,8 +1070,17 @@ public class KurentoSessionManager extends SessionManager {
|
|||
public void reconnectStream(Participant participant, String streamId, String sdpOffer, Integer transactionId) {
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
|
||||
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
||||
|
||||
if (streamId.equals(participant.getPublisherStreamId())) {
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpOffer = sdpMunging.forceCodec(participant, sdpOffer, true, kSession, isPublisher,
|
||||
true, isTranscodingAllowed, forcedVideoCodec);
|
||||
}
|
||||
|
||||
if (isPublisher) {
|
||||
|
||||
// Reconnect publisher
|
||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
||||
|
@ -1069,7 +1099,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
|
||||
SdpType sdpType = kurentoOptions.isOffer ? SdpType.OFFER : SdpType.ANSWER;
|
||||
String sdpAnswer = kParticipant.publishToRoom(sdpType, sdpOffer, kurentoOptions.doLoopback, true);
|
||||
|
||||
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(), sdpAnswer);
|
||||
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
||||
new HashSet<Participant>(), transactionId, null);
|
||||
|
@ -1086,6 +1116,8 @@ public class KurentoSessionManager extends SessionManager {
|
|||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
|
||||
}
|
||||
|
||||
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(), sdpAnswer);
|
||||
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
|
||||
} else {
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||
|
|
|
@ -116,6 +116,8 @@ public class ConfigRestController {
|
|||
json.addProperty("OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH", openviduConfig.getVideoMinSendBandwidth());
|
||||
json.addProperty("OPENVIDU_SESSIONS_GARBAGE_INTERVAL", openviduConfig.getSessionGarbageInterval());
|
||||
json.addProperty("OPENVIDU_SESSIONS_GARBAGE_THRESHOLD", openviduConfig.getSessionGarbageThreshold());
|
||||
json.addProperty("OPENVIDU_FORCED_CODEC", openviduConfig.getOpenviduForcedCodec().name());
|
||||
json.addProperty("OPENVIDU_ALLOW_TRANSCODING", openviduConfig.isOpenviduAllowingTranscoding());
|
||||
json.addProperty("OPENVIDU_RECORDING", openviduConfig.isRecordingModuleEnabled());
|
||||
if (openviduConfig.isRecordingModuleEnabled()) {
|
||||
json.addProperty("OPENVIDU_RECORDING_VERSION", openviduConfig.getOpenViduRecordingVersion());
|
||||
|
|
|
@ -60,6 +60,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;
|
||||
|
@ -715,6 +716,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");
|
||||
|
@ -722,6 +725,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) {
|
||||
throw new Exception("Type error in some parameter: " + e.getMessage());
|
||||
}
|
||||
|
@ -764,6 +769,16 @@ public class SessionRestController {
|
|||
}
|
||||
builder = builder.customSessionId(customSessionId);
|
||||
}
|
||||
if (forcedVideoCodec != null) {
|
||||
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
|
||||
} else {
|
||||
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
|
||||
}
|
||||
if (allowTranscoding != null) {
|
||||
builder = builder.allowTranscoding(allowTranscoding);
|
||||
} else {
|
||||
builder = builder.allowTranscoding(openviduConfig.isOpenviduAllowingTranscoding());
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new Exception("RecordingMode " + params.get("recordingMode") + " | " + "Default OutputMode "
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* (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.server.utils;
|
||||
|
||||
import io.openvidu.client.OpenViduException;
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import io.openvidu.client.OpenViduException.Code;
|
||||
import io.openvidu.server.core.Participant;
|
||||
import io.openvidu.server.core.Session;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SDPMunging {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SDPMunging.class);
|
||||
|
||||
private Set<VideoCodec> supportedVideoCodecs = new HashSet<>(Arrays.asList(
|
||||
VideoCodec.VP8,
|
||||
VideoCodec.H264
|
||||
));
|
||||
|
||||
/**
|
||||
* `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 + " ");
|
||||
}
|
||||
|
||||
// Replace the original m= line with the one we just built.
|
||||
lines[sl] = newLine.toString().trim();
|
||||
}
|
||||
|
||||
return String.join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SDP modified to force a specific codec
|
||||
*/
|
||||
public String forceCodec(Participant participant, String sdp, boolean isOffer, Session session, boolean isPublisher,
|
||||
boolean isReconnecting, boolean isTranscodingAllowed, VideoCodec forcedVideoCodec) throws OpenViduException {
|
||||
try {
|
||||
if (supportedVideoCodecs.contains(forcedVideoCodec)) {
|
||||
String mungedSdpOffer;
|
||||
log.debug("PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'. Is Offer SDP: '{}'. " +
|
||||
"Is Answer SDP: '{}'. Is Reconnecting '{}'. SDP before munging: \n {}", participant.getParticipantPublicId(),
|
||||
session.getSessionId(), isPublisher, !isPublisher, isOffer, !isOffer, isReconnecting, sdp);
|
||||
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp);
|
||||
log.debug("PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'. Is Offer SDP: '{}'. " +
|
||||
"Is Answer SDP: '{}'. Is Reconnecting '{}'. SDP after munging: \n {}", participant.getParticipantPublicId(),
|
||||
session.getSessionId(), isPublisher, !isPublisher, isOffer, !isOffer, isReconnecting, mungedSdpOffer);
|
||||
return mungedSdpOffer;
|
||||
} else {
|
||||
throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, "Codec not supported by Media Server");
|
||||
}
|
||||
|
||||
} catch (OpenViduException e) {
|
||||
String errorMessage = "Error forcing codec: '" + forcedVideoCodec + "', for PARTICIPANT: '" + participant.getParticipantPublicId()
|
||||
+ "' in Session: '" + session.getSessionId() + "'. Is publishing: '" + isPublisher + "'. Is Subscriber: '" + !isPublisher
|
||||
+ "'. Is Offer: '" + isOffer + "'. Is Answer: '" + !isOffer + "'. Is Reconnecting: '"
|
||||
+ isReconnecting + "'.\nException: " + e.getMessage() + "\nSDP:\n" + sdp;
|
||||
if(!isTranscodingAllowed) {
|
||||
throw new OpenViduException(Code.FORCED_CODEC_NOT_FOUND_IN_SDPOFFER, errorMessage);
|
||||
}
|
||||
log.info("Codec: '{}' is not supported for PARTICIPANT: '{}' in Session: '{}'. Is publishing: '{}'. Is Subscriber: '{}' "
|
||||
+ "Is Offer SDP: '{}'. Is Answer SDP: '{}'. Is Reconnecting: '{}'. Transcoding will be allowed", forcedVideoCodec, participant.getParticipantPublicId(),
|
||||
session.getSessionId(), isPublisher, !isPublisher, isOffer, !isOffer, isReconnecting);
|
||||
return sdp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -45,6 +45,9 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
|||
OPENVIDU_SESSIONS_GARBAGE_INTERVAL=900
|
||||
OPENVIDU_SESSIONS_GARBAGE_THRESHOLD=3600
|
||||
|
||||
OPENVIDU_FORCED_CODEC=VP8
|
||||
OPENVIDU_ALLOW_TRANSCODING=false
|
||||
|
||||
COTURN_REDIS_IP=127.0.0.1
|
||||
COTURN_REDIS_DBNAME=0
|
||||
COTURN_REDIS_PASSWORD=turn
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
package io.openvidu.server.test.unit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
||||
import io.openvidu.client.OpenViduException;
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import io.openvidu.server.utils.SDPMunging;
|
||||
|
||||
public class SDPMungingTest {
|
||||
|
||||
private SDPMunging sdpMungin = new SDPMunging();
|
||||
|
||||
private String oldSdp;
|
||||
|
||||
private String newSdp;
|
||||
|
||||
List<String> h264codecPayloads;
|
||||
|
||||
List<String> forceCodecPayloads;
|
||||
|
||||
String validSDPH264Files[] = new String[]{
|
||||
"sdp_kurento_h264.txt",
|
||||
"sdp_chrome84.txt",
|
||||
"sdp_firefox79.txt",
|
||||
"sdp_safari13-1.txt"
|
||||
};
|
||||
|
||||
String validSDPVP8Files[] = new String[]{
|
||||
"sdp_kurento_h264.txt",
|
||||
"sdp_chrome84.txt",
|
||||
"sdp_firefox79.txt",
|
||||
"sdp_safari13-1.txt"
|
||||
};
|
||||
|
||||
String validSDPVP9Files[] = new String[] {
|
||||
"sdp_chrome84.txt",
|
||||
"sdp_firefox79.txt"
|
||||
};
|
||||
|
||||
String notValidVP9Files[] = new String[] {
|
||||
"sdp_kurento_h264.txt",
|
||||
"sdp_safari13-1.txt"
|
||||
};
|
||||
|
||||
@Test
|
||||
@DisplayName("[setCodecPreference] Force VP8 Codec prevalence in 'm=video' line")
|
||||
public void checkPreferenceCodecVP8() throws IOException {
|
||||
for(String sdpFileName: validSDPVP8Files) {
|
||||
initTestsSetCodecPrevalence(VideoCodec.VP8, sdpFileName);
|
||||
checkPrevalenceCodecInML();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("[setCodecPreference] Force VP8 Codec prevalence in 'm=video' line")
|
||||
public void checkPreferenceCodecVP9() throws IOException {
|
||||
for(String sdpFileName: validSDPVP9Files) {
|
||||
initTestsSetCodecPrevalence(VideoCodec.VP9, sdpFileName);
|
||||
checkPrevalenceCodecInML();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("[setCodecPreference] Force H264 Codec prevalence in 'm=video' line")
|
||||
public void checkPreferenceCodecH264() throws IOException {
|
||||
for(String sdpFileName: validSDPH264Files) {
|
||||
initTestsSetCodecPrevalence(VideoCodec.H264, sdpFileName);
|
||||
checkPrevalenceCodecInML();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("[setCodecPreference] Exception when codec does not exists on SDP")
|
||||
public void checkPreferenceCodecException() throws IOException {
|
||||
for(String sdpFile: notValidVP9Files) {
|
||||
Exception exception = assertThrows(OpenViduException.class, () -> {
|
||||
initTestsSetCodecPrevalence(VideoCodec.VP9, sdpFile);
|
||||
});
|
||||
String expectedMessage = "The specified forced codec VP9 is not present in the SDP";
|
||||
assertTrue(exception.getMessage().contains(expectedMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private String getSdpFile(String sdpNameFile) throws IOException {
|
||||
Path sdpFile = Files.createTempFile("sdp-test", ".tmp");
|
||||
Files.copy(getClass().getResourceAsStream("/sdp/" + sdpNameFile), sdpFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
String sdpUnformatted = new String(Files.readAllBytes(sdpFile));
|
||||
return String.join("\r\n", sdpUnformatted.split("\\R+")) + "\r\n";
|
||||
}
|
||||
|
||||
private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException {
|
||||
this.oldSdp = getSdpFile(sdpNameFile);
|
||||
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp);
|
||||
this.forceCodecPayloads = new ArrayList<>();
|
||||
|
||||
// Get all Payload-Type for video Codec
|
||||
for(String oldSdpLine: oldSdp.split("\\R+")) {
|
||||
if(oldSdpLine.startsWith("a=rtpmap") && oldSdpLine.endsWith(codec.name() + "/90000")) {
|
||||
String pt = oldSdpLine.split(":")[1].split(" ")[0];
|
||||
this.forceCodecPayloads.add(pt);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all Payload-Types rtx related with codec
|
||||
// Not the best way to do it, but enough to check if the sdp
|
||||
// generated is correct
|
||||
String[] oldSdpLines = oldSdp.split("\\R+");
|
||||
List<String> rtxForcedCodecs = new ArrayList<>();
|
||||
for(String oldSdpLine: oldSdpLines) {
|
||||
if(oldSdpLine.startsWith("a=rtpmap") && oldSdpLine.endsWith("rtx/90000")) {
|
||||
String rtxPayload = oldSdpLine.split(":")[1].split(" ")[0];
|
||||
for (String auxOldSdpLine: oldSdpLines) {
|
||||
if (auxOldSdpLine.contains("a=fmtp:" + rtxPayload + " apt=")) {
|
||||
for (String auxForcedCodec: this.forceCodecPayloads) {
|
||||
if (auxOldSdpLine.contains("a=fmtp:" + rtxPayload + " apt=" + auxForcedCodec)) {
|
||||
String pt = oldSdpLine.split(":")[1].split(" ")[0];
|
||||
rtxForcedCodecs.add(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.forceCodecPayloads.addAll(rtxForcedCodecs);
|
||||
}
|
||||
|
||||
private void checkPrevalenceCodecInML() {
|
||||
|
||||
String newml = null;
|
||||
String[] newSdpLines = this.newSdp.split("\\R+");
|
||||
for(String newSdpLine: newSdpLines) {
|
||||
if (newSdpLine.startsWith("m=video")) {
|
||||
newml = newSdpLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newml == null) {
|
||||
fail("'m=video' line not found in SDP");
|
||||
}
|
||||
|
||||
List<String> newMlCodecPrevalenceList = new ArrayList<>();
|
||||
String[] lmParams = newml.split(" ");
|
||||
int numOfCodecsWithPrevalence = this.forceCodecPayloads.size();
|
||||
int indexStartCodecs = 3;
|
||||
int indexEndPreferencedCodecs = 3 + numOfCodecsWithPrevalence;
|
||||
for(int i = indexStartCodecs; i < indexEndPreferencedCodecs; i++) {
|
||||
newMlCodecPrevalenceList.add(lmParams[i]);
|
||||
}
|
||||
|
||||
for(int j = 0; j < numOfCodecsWithPrevalence; j++) {
|
||||
String codecToCheck = newMlCodecPrevalenceList.get(j);
|
||||
boolean codecFoundInPrevalenceList = false;
|
||||
for(String codecToForce: this.forceCodecPayloads) {
|
||||
if (codecToCheck.equals(codecToForce)) {
|
||||
codecFoundInPrevalenceList = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(codecFoundInPrevalenceList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -40,6 +40,9 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
|||
OPENVIDU_SESSIONS_GARBAGE_INTERVAL=900
|
||||
OPENVIDU_SESSIONS_GARBAGE_THRESHOLD=3600
|
||||
|
||||
OPENVIDU_FORCED_CODEC=VP8
|
||||
OPENVIDU_ALLOW_TRANSCODING=false
|
||||
|
||||
COTURN_REDIS_IP=127.0.0.1
|
||||
COTURN_REDIS_DBNAME=0
|
||||
COTURN_REDIS_PASSWORD=turn
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
v=0
|
||||
o=- 5217540180782877494 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0 1
|
||||
a=msid-semantic: WMS eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:RWim
|
||||
a=ice-pwd:seCgmyE+AkRJKgqD4SdIuALd
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 59:47:FE:36:82:34:B1:7B:4C:D5:D4:76:78:24:89:67:5E:3C:84:4F:52:BD:86:83:67:10:98:8C:79:9D:89:7D
|
||||
a=setup:actpass
|
||||
a=mid:0
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendonly
|
||||
a=msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 7bbc37fe-6e36-479f-990f-988a07ac3f00
|
||||
a=rtcp-mux
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=rtcp-fb:111 transport-cc
|
||||
a=fmtp:111 minptime=10;useinbandfec=1
|
||||
a=rtpmap:103 ISAC/16000
|
||||
a=rtpmap:104 ISAC/32000
|
||||
a=rtpmap:9 G722/8000
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:106 CN/32000
|
||||
a=rtpmap:105 CN/16000
|
||||
a=rtpmap:13 CN/8000
|
||||
a=rtpmap:110 telephone-event/48000
|
||||
a=rtpmap:112 telephone-event/32000
|
||||
a=rtpmap:113 telephone-event/16000
|
||||
a=rtpmap:126 telephone-event/8000
|
||||
a=ssrc:913601848 cname:BkItF+kVUhq9L3k4
|
||||
a=ssrc:913601848 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 7bbc37fe-6e36-479f-990f-988a07ac3f00
|
||||
a=ssrc:913601848 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul
|
||||
a=ssrc:913601848 label:7bbc37fe-6e36-479f-990f-988a07ac3f00
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:RWim
|
||||
a=ice-pwd:seCgmyE+AkRJKgqD4SdIuALd
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 59:47:FE:36:82:34:B1:7B:4C:D5:D4:76:78:24:89:67:5E:3C:84:4F:52:BD:86:83:67:10:98:8C:79:9D:89:7D
|
||||
a=setup:actpass
|
||||
a=mid:1
|
||||
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:13 urn:3gpp:video-orientation
|
||||
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
||||
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
||||
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
||||
a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
|
||||
a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
||||
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendonly
|
||||
a=msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685
|
||||
a=rtcp-mux
|
||||
a=rtcp-rsize
|
||||
a=rtpmap:96 VP8/90000
|
||||
a=rtcp-fb:96 goog-remb
|
||||
a=rtcp-fb:96 transport-cc
|
||||
a=rtcp-fb:96 ccm fir
|
||||
a=rtcp-fb:96 nack
|
||||
a=rtcp-fb:96 nack pli
|
||||
a=rtpmap:97 rtx/90000
|
||||
a=fmtp:97 apt=96
|
||||
a=rtpmap:98 VP9/90000
|
||||
a=rtcp-fb:98 goog-remb
|
||||
a=rtcp-fb:98 transport-cc
|
||||
a=rtcp-fb:98 ccm fir
|
||||
a=rtcp-fb:98 nack
|
||||
a=rtcp-fb:98 nack pli
|
||||
a=fmtp:98 profile-id=0
|
||||
a=rtpmap:99 rtx/90000
|
||||
a=fmtp:99 apt=98
|
||||
a=rtpmap:100 VP9/90000
|
||||
a=rtcp-fb:100 goog-remb
|
||||
a=rtcp-fb:100 transport-cc
|
||||
a=rtcp-fb:100 ccm fir
|
||||
a=rtcp-fb:100 nack
|
||||
a=rtcp-fb:100 nack pli
|
||||
a=fmtp:100 profile-id=2
|
||||
a=rtpmap:101 rtx/90000
|
||||
a=fmtp:101 apt=100
|
||||
a=rtpmap:102 H264/90000
|
||||
a=rtcp-fb:102 goog-remb
|
||||
a=rtcp-fb:102 transport-cc
|
||||
a=rtcp-fb:102 ccm fir
|
||||
a=rtcp-fb:102 nack
|
||||
a=rtcp-fb:102 nack pli
|
||||
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
|
||||
a=rtpmap:122 rtx/90000
|
||||
a=fmtp:122 apt=102
|
||||
a=rtpmap:127 H264/90000
|
||||
a=rtcp-fb:127 goog-remb
|
||||
a=rtcp-fb:127 transport-cc
|
||||
a=rtcp-fb:127 ccm fir
|
||||
a=rtcp-fb:127 nack
|
||||
a=rtcp-fb:127 nack pli
|
||||
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
|
||||
a=rtpmap:121 rtx/90000
|
||||
a=fmtp:121 apt=127
|
||||
a=rtpmap:125 H264/90000
|
||||
a=rtcp-fb:125 goog-remb
|
||||
a=rtcp-fb:125 transport-cc
|
||||
a=rtcp-fb:125 ccm fir
|
||||
a=rtcp-fb:125 nack
|
||||
a=rtcp-fb:125 nack pli
|
||||
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
||||
a=rtpmap:107 rtx/90000
|
||||
a=fmtp:107 apt=125
|
||||
a=rtpmap:108 H264/90000
|
||||
a=rtcp-fb:108 goog-remb
|
||||
a=rtcp-fb:108 transport-cc
|
||||
a=rtcp-fb:108 ccm fir
|
||||
a=rtcp-fb:108 nack
|
||||
a=rtcp-fb:108 nack pli
|
||||
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
|
||||
a=rtpmap:109 rtx/90000
|
||||
a=fmtp:109 apt=108
|
||||
a=rtpmap:124 red/90000
|
||||
a=rtpmap:120 rtx/90000
|
||||
a=fmtp:120 apt=124
|
||||
a=rtpmap:123 ulpfec/90000
|
||||
a=ssrc-group:FID 796117241 4235816742
|
||||
a=ssrc:796117241 cname:BkItF+kVUhq9L3k4
|
||||
a=ssrc:796117241 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685
|
||||
a=ssrc:796117241 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul
|
||||
a=ssrc:796117241 label:29491954-cc1e-4ca3-b290-cb8d5d0ad685
|
||||
a=ssrc:4235816742 cname:BkItF+kVUhq9L3k4
|
||||
a=ssrc:4235816742 msid:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul 29491954-cc1e-4ca3-b290-cb8d5d0ad685
|
||||
a=ssrc:4235816742 mslabel:eEMVYR4txYWGErUa55KnHm0mMBdfrqSbtQul
|
||||
a=ssrc:4235816742 label:29491954-cc1e-4ca3-b290-cb8d5d0ad685
|
|
@ -0,0 +1,67 @@
|
|||
v=0
|
||||
o=mozilla...THIS_IS_SDPARTA-79.0 1574413241511582424 0 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=sendrecv
|
||||
a=fingerprint:sha-256 43:63:A0:1A:D4:F3:6A:0B:B9:DC:AD:8B:A4:20:22:17:B9:BD:FC:81:9F:EC:E9:46:E0:61:3B:8B:2A:05:9A:D9
|
||||
a=group:BUNDLE 0 1
|
||||
a=ice-options:trickle
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101
|
||||
c=IN IP4 0.0.0.0
|
||||
a=sendonly
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
a=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level
|
||||
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1
|
||||
a=fmtp:101 0-15
|
||||
a=ice-pwd:4be109f1ce637a5423d9229aa10c43bd
|
||||
a=ice-ufrag:03567d22
|
||||
a=mid:0
|
||||
a=msid:{77708aaa-ed09-403a-b19a-26b8f44aff96} {f18baa5e-b689-4b17-9db8-83620b6c1f2b}
|
||||
a=rtcp-mux
|
||||
a=rtpmap:109 opus/48000/2
|
||||
a=rtpmap:9 G722/8000/1
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:101 telephone-event/8000
|
||||
a=setup:actpass
|
||||
a=ssrc:2626852385 cname:{4ff79710-1727-45f8-bbe7-7532884b9652}
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97
|
||||
c=IN IP4 0.0.0.0
|
||||
a=sendonly
|
||||
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:toffset
|
||||
a=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
||||
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
|
||||
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
|
||||
a=fmtp:120 max-fs=12288;max-fr=60
|
||||
a=fmtp:121 max-fs=12288;max-fr=60
|
||||
a=ice-pwd:4be109f1ce637a5423d9229aa10c43bd
|
||||
a=ice-ufrag:03567d22
|
||||
a=mid:1
|
||||
a=msid:{77708aaa-ed09-403a-b19a-26b8f44aff96} {a68009be-6483-45fd-a48d-7bc60fa6f055}
|
||||
a=rtcp-fb:120 nack
|
||||
a=rtcp-fb:120 nack pli
|
||||
a=rtcp-fb:120 ccm fir
|
||||
a=rtcp-fb:120 goog-remb
|
||||
a=rtcp-fb:121 nack
|
||||
a=rtcp-fb:121 nack pli
|
||||
a=rtcp-fb:121 ccm fir
|
||||
a=rtcp-fb:121 goog-remb
|
||||
a=rtcp-fb:126 nack
|
||||
a=rtcp-fb:126 nack pli
|
||||
a=rtcp-fb:126 ccm fir
|
||||
a=rtcp-fb:126 goog-remb
|
||||
a=rtcp-fb:97 nack
|
||||
a=rtcp-fb:97 nack pli
|
||||
a=rtcp-fb:97 ccm fir
|
||||
a=rtcp-fb:97 goog-remb
|
||||
a=rtcp-mux
|
||||
a=rtpmap:120 VP8/90000
|
||||
a=rtpmap:121 VP9/90000
|
||||
a=rtpmap:126 H264/90000
|
||||
a=rtpmap:97 H264/90000
|
||||
a=setup:actpass
|
||||
a=ssrc:3743317987 cname:{4ff79710-1727-45f8-bbe7-7532884b9652}
|
|
@ -0,0 +1,61 @@
|
|||
v=0
|
||||
o=- 3808465464 3808465464 IN IP4 0.0.0.0
|
||||
s=Kurento Media Server
|
||||
c=IN IP4 0.0.0.0
|
||||
t=0 0
|
||||
a=msid-semantic: WMS m0W2gMak7LgkgzgJeDQhxBX0ivcsejWjQ0jD
|
||||
a=group:BUNDLE 0 1
|
||||
m=audio 1 UDP/TLS/RTP/SAVPF 111 0
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=recvonly
|
||||
a=mid:0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=setup:active
|
||||
a=rtcp-mux
|
||||
a=fmtp:111 minptime=10;useinbandfec=1
|
||||
a=ssrc:1929271881 cname:user4129876135@host-ed881df6
|
||||
a=ice-ufrag:cXmf
|
||||
a=ice-pwd:9giZcfpsuoHRuxCgbnCLRy
|
||||
a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51
|
||||
m=video 1 UDP/TLS/RTP/SAVPF 96 102 127 125 108
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=recvonly
|
||||
a=mid:1
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=rtpmap:96 VP8/90000
|
||||
a=rtpmap:102 H264/90000
|
||||
a=rtpmap:127 H264/90000
|
||||
a=rtpmap:125 H264/90000
|
||||
a=rtpmap:108 H264/90000
|
||||
a=rtcp-fb:96 goog-remb
|
||||
a=rtcp-fb:96 ccm fir
|
||||
a=rtcp-fb:96 nack
|
||||
a=rtcp-fb:96 nack pli
|
||||
a=rtcp-fb:102 goog-remb
|
||||
a=rtcp-fb:102 ccm fir
|
||||
a=rtcp-fb:102 nack
|
||||
a=rtcp-fb:102 nack pli
|
||||
a=rtcp-fb:127 goog-remb
|
||||
a=rtcp-fb:127 ccm fir
|
||||
a=rtcp-fb:127 nack
|
||||
a=rtcp-fb:127 nack pli
|
||||
a=rtcp-fb:125 goog-remb
|
||||
a=rtcp-fb:125 ccm fir
|
||||
a=rtcp-fb:125 nack
|
||||
a=rtcp-fb:125 nack pli
|
||||
a=rtcp-fb:108 goog-remb
|
||||
a=rtcp-fb:108 ccm fir
|
||||
a=rtcp-fb:108 nack
|
||||
a=rtcp-fb:108 nack pli
|
||||
a=setup:active
|
||||
a=rtcp-mux
|
||||
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
|
||||
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
|
||||
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
||||
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
|
||||
a=ssrc:3762875210 cname:user4129876135@host-ed881df6
|
||||
a=ice-ufrag:cXmf
|
||||
a=ice-pwd:9giZcfpsuoHRuxCgbnCLRy
|
||||
a=fingerprint:sha-256 C8:D4:B5:56:A7:89:E5:E1:C8:28:0A:47:2B:49:F6:7A:E2:2E:B3:0A:40:10:AD:79:82:E7:FD:A0:ED:6C:F6:51
|
|
@ -0,0 +1,107 @@
|
|||
v=0
|
||||
o=- 4920969914039852086 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0 1
|
||||
a=msid-semantic: WMS 90c21009-8d9f-4b31-8091-d98deb8361c8
|
||||
m=audio 61842 UDP/TLS/RTP/SAVPF 111 103 9 102 0 8 105 13 110 113 126
|
||||
c=IN IP4 192.168.1.105
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=candidate:2222700650 1 udp 2113937151 192.168.1.105 61842 typ host generation 0 network-cost 999
|
||||
a=ice-ufrag:zbK7
|
||||
a=ice-pwd:o4ZsJFGBXHNzOWqX23brKpiG
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 18:BD:1F:64:28:C6:BC:7B:AD:83:42:E0:B1:78:BA:13:F4:BF:F5:5E:AD:20:62:CC:AF:DF:99:AD:20:CB:1B:7E
|
||||
a=setup:actpass
|
||||
a=mid:0
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendonly
|
||||
a=msid:90c21009-8d9f-4b31-8091-d98deb8361c8 1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba
|
||||
a=rtcp-mux
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=rtcp-fb:111 transport-cc
|
||||
a=fmtp:111 minptime=10;useinbandfec=1
|
||||
a=rtpmap:103 ISAC/16000
|
||||
a=rtpmap:9 G722/8000
|
||||
a=rtpmap:102 ILBC/8000
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:105 CN/16000
|
||||
a=rtpmap:13 CN/8000
|
||||
a=rtpmap:110 telephone-event/48000
|
||||
a=rtpmap:113 telephone-event/16000
|
||||
a=rtpmap:126 telephone-event/8000
|
||||
a=ssrc:1052596434 cname:L3DqI2bKTDkcNVsn
|
||||
a=ssrc:1052596434 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba
|
||||
a=ssrc:1052596434 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8
|
||||
a=ssrc:1052596434 label:1d7a65f7-59c9-42a7-abbb-d9fb5548e3ba
|
||||
m=video 62559 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 125 104
|
||||
c=IN IP4 192.168.1.105
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=candidate:2222700650 1 udp 2113937151 192.168.1.105 62559 typ host generation 0 network-cost 999
|
||||
a=ice-ufrag:zbK7
|
||||
a=ice-pwd:o4ZsJFGBXHNzOWqX23brKpiG
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 18:BD:1F:64:28:C6:BC:7B:AD:83:42:E0:B1:78:BA:13:F4:BF:F5:5E:AD:20:62:CC:AF:DF:99:AD:20:CB:1B:7E
|
||||
a=setup:actpass
|
||||
a=mid:1
|
||||
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
|
||||
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:13 urn:3gpp:video-orientation
|
||||
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
||||
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
|
||||
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
|
||||
a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
|
||||
a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
|
||||
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendonly
|
||||
a=msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7
|
||||
a=rtcp-mux
|
||||
a=rtcp-rsize
|
||||
a=rtpmap:96 H264/90000
|
||||
a=rtcp-fb:96 goog-remb
|
||||
a=rtcp-fb:96 transport-cc
|
||||
a=rtcp-fb:96 ccm fir
|
||||
a=rtcp-fb:96 nack
|
||||
a=rtcp-fb:96 nack pli
|
||||
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
|
||||
a=rtpmap:97 rtx/90000
|
||||
a=fmtp:97 apt=96
|
||||
a=rtpmap:98 H264/90000
|
||||
a=rtcp-fb:98 goog-remb
|
||||
a=rtcp-fb:98 transport-cc
|
||||
a=rtcp-fb:98 ccm fir
|
||||
a=rtcp-fb:98 nack
|
||||
a=rtcp-fb:98 nack pli
|
||||
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
|
||||
a=rtpmap:99 rtx/90000
|
||||
a=fmtp:99 apt=98
|
||||
a=rtpmap:100 VP8/90000
|
||||
a=rtcp-fb:100 goog-remb
|
||||
a=rtcp-fb:100 transport-cc
|
||||
a=rtcp-fb:100 ccm fir
|
||||
a=rtcp-fb:100 nack
|
||||
a=rtcp-fb:100 nack pli
|
||||
a=rtpmap:101 rtx/90000
|
||||
a=fmtp:101 apt=100
|
||||
a=rtpmap:127 red/90000
|
||||
a=rtpmap:125 rtx/90000
|
||||
a=fmtp:125 apt=127
|
||||
a=rtpmap:104 ulpfec/90000
|
||||
a=ssrc-group:FID 2734983896 3694891391
|
||||
a=ssrc:2734983896 cname:L3DqI2bKTDkcNVsn
|
||||
a=ssrc:2734983896 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7
|
||||
a=ssrc:2734983896 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8
|
||||
a=ssrc:2734983896 label:fc45e668-c301-41fe-ae8a-1265a5a355e7
|
||||
a=ssrc:3694891391 cname:L3DqI2bKTDkcNVsn
|
||||
a=ssrc:3694891391 msid:90c21009-8d9f-4b31-8091-d98deb8361c8 fc45e668-c301-41fe-ae8a-1265a5a355e7
|
||||
a=ssrc:3694891391 mslabel:90c21009-8d9f-4b31-8091-d98deb8361c8
|
||||
a=ssrc:3694891391 label:fc45e668-c301-41fe-ae8a-1265a5a355e7
|
|
@ -30,7 +30,7 @@ import org.openqa.selenium.remote.RemoteWebDriver;
|
|||
|
||||
public class FirefoxUser extends BrowserUser {
|
||||
|
||||
public FirefoxUser(String userName, int timeOfWaitInSeconds) {
|
||||
public FirefoxUser(String userName, int timeOfWaitInSeconds, boolean disableOpenH264) {
|
||||
super(userName, timeOfWaitInSeconds);
|
||||
|
||||
DesiredCapabilities capabilities = DesiredCapabilities.firefox();
|
||||
|
@ -43,6 +43,10 @@ public class FirefoxUser extends BrowserUser {
|
|||
// This flag force to use fake user media (synthetic video of multiple color)
|
||||
profile.setPreference("media.navigator.streams.fake", true);
|
||||
|
||||
if (disableOpenH264) {
|
||||
profile.setPreference("media.gmp-gmpopenh264.enabled", false);
|
||||
}
|
||||
|
||||
capabilities.setCapability(FirefoxDriver.PROFILE, profile);
|
||||
|
||||
String REMOTE_URL = System.getProperty("REMOTE_URL_FIREFOX");
|
||||
|
|
|
@ -64,7 +64,7 @@ import io.openvidu.test.browsers.utils.Unzipper;
|
|||
|
||||
public class AbstractOpenViduTestAppE2eTest {
|
||||
|
||||
final protected String DEFAULT_JSON_SESSION = "{'id':'STR','object':'session','sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultOutputMode':'STR','defaultRecordingLayout':'STR','customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':false}";
|
||||
final protected String DEFAULT_JSON_SESSION = "{'id':'STR','object':'session','sessionId':'STR','createdAt':0,'mediaMode':'STR','recordingMode':'STR','defaultOutputMode':'STR','defaultRecordingLayout':'STR','customSessionId':'STR','connections':{'numberOfElements':0,'content':[]},'recording':false,'forcedVideoCodec':'STR','allowTranscoding':false}";
|
||||
final protected String DEFAULT_JSON_PENDING_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'pending','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':null,'location':null,'platform':null,'token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':null,'publishers':null,'subscribers':null}";
|
||||
final protected String DEFAULT_JSON_ACTIVE_CONNECTION = "{'id':'STR','object':'connection','type':'WEBRTC','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','platform':'STR','token':'STR','serverData':'STR','record':true,'role':'STR','kurentoOptions':null,'rtspUri':null,'adaptativeBitrate':null,'onlyPlayWithSubscribers':null,'networkCache':null,'clientData':'STR','publishers':[],'subscribers':[]}";
|
||||
final protected String DEFAULT_JSON_IPCAM_CONNECTION = "{'id':'STR','object':'connection','type':'IPCAM','status':'active','connectionId':'STR','sessionId':'STR','createdAt':0,'activeAt':0,'location':'STR','platform':'IPCAM','token':null,'serverData':'STR','record':true,'role':null,'kurentoOptions':null,'rtspUri':'STR','adaptativeBitrate':true,'onlyPlayWithSubscribers':true,'networkCache':2000,'clientData':null,'publishers':[],'subscribers':[]}";
|
||||
|
@ -177,7 +177,10 @@ public class AbstractOpenViduTestAppE2eTest {
|
|||
browserUser = new ChromeUser("TestUser", 50, false);
|
||||
break;
|
||||
case "firefox":
|
||||
browserUser = new FirefoxUser("TestUser", 50);
|
||||
browserUser = new FirefoxUser("TestUser", 50, false);
|
||||
break;
|
||||
case "firefoxDisabledOpenH264":
|
||||
browserUser = new FirefoxUser("TestUser", 50, true);
|
||||
break;
|
||||
case "opera":
|
||||
browserUser = new OperaUser("TestUser", 50);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package io.openvidu.test.e2e;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
@ -70,6 +71,7 @@ import io.openvidu.java.client.RecordingMode;
|
|||
import io.openvidu.java.client.RecordingProperties;
|
||||
import io.openvidu.java.client.Session;
|
||||
import io.openvidu.java.client.SessionProperties;
|
||||
import io.openvidu.java.client.VideoCodec;
|
||||
import io.openvidu.test.browsers.FirefoxUser;
|
||||
import io.openvidu.test.browsers.utils.CustomHttpClient;
|
||||
import io.openvidu.test.browsers.utils.layout.CustomLayoutHandler;
|
||||
|
@ -366,7 +368,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
|
||||
Thread threadFirefox = new Thread(() -> {
|
||||
MyUser user2 = new MyUser(new FirefoxUser("TestUser", 30));
|
||||
MyUser user2 = new MyUser(new FirefoxUser("TestUser", 30, false));
|
||||
otherUsers.add(user2);
|
||||
user2.getDriver().get(APP_URL);
|
||||
WebElement urlInput = user2.getDriver().findElement(By.id("openvidu-url"));
|
||||
|
@ -1417,7 +1419,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
};
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
MyUser user2 = new MyUser(new FirefoxUser("FirefoxUser", 30));
|
||||
MyUser user2 = new MyUser(new FirefoxUser("FirefoxUser", 30, false));
|
||||
otherUsers.add(user2);
|
||||
user2.getDriver().get(APP_URL);
|
||||
WebElement urlInput = user2.getDriver().findElement(By.id("openvidu-url"));
|
||||
|
@ -2923,7 +2925,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
+ "{'createdAt':0,'streamId':'STR','mediaOptions':{'hasAudio':false,'audioActive':false,'hasVideo':false,'videoActive':false,'typeOfVideo':'STR','frameRate':0,"
|
||||
+ "'videoDimensions':'STR','filter':{}}}],'subscribers':[{'createdAt':0,'streamId':'STR','publisher':'STR'}]},{'connectionId':'STR','createdAt':0,'location':'STR',"
|
||||
+ "'platform':'STR','token':'STR','role':'STR','serverData':'STR','clientData':'STR','publishers':[{'createdAt':0,'streamId':'STR','mediaOptions':{'hasAudio':false,"
|
||||
+ "'audioActive':false,'hasVideo':false,'videoActive':false,'typeOfVideo':'STR','frameRate':0,'videoDimensions':'STR','filter':{}}}],'subscribers':[{'createdAt':0,'streamId':'STR','publisher':'STR'}]}]},'recording':false}");
|
||||
+ "'audioActive':false,'hasVideo':false,'videoActive':false,'typeOfVideo':'STR','frameRate':0,'videoDimensions':'STR','filter':{}}}],'subscribers':[{'createdAt':0,'streamId':'STR','publisher':'STR'}]}]},'recording':false,'forcedVideoCodec':'STR','allowTranscoding':false}");
|
||||
String streamId = res.get("connections").getAsJsonObject().get("content").getAsJsonArray().get(0)
|
||||
.getAsJsonObject().get("publishers").getAsJsonArray().get(0).getAsJsonObject().get("streamId")
|
||||
.getAsString();
|
||||
|
@ -3044,7 +3046,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
restClient.rest(HttpMethod.GET, "/openvidu/api/config", null, HttpStatus.SC_OK, true, false, true,
|
||||
"{'VERSION':'STR','DOMAIN_OR_PUBLIC_IP':'STR','HTTPS_PORT':0,'OPENVIDU_PUBLICURL':'STR','OPENVIDU_CDR':false,'OPENVIDU_STREAMS_VIDEO_MAX_RECV_BANDWIDTH':0,'OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH':0,"
|
||||
+ "'OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH':0,'OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH':0,'OPENVIDU_SESSIONS_GARBAGE_INTERVAL':0,'OPENVIDU_SESSIONS_GARBAGE_THRESHOLD':0,"
|
||||
+ "'OPENVIDU_RECORDING':false,'OPENVIDU_RECORDING_VERSION':'STR','OPENVIDU_RECORDING_PATH':'STR','OPENVIDU_RECORDING_PUBLIC_ACCESS':false,'OPENVIDU_RECORDING_NOTIFICATION':'STR',"
|
||||
+ "'OPENVIDU_FORCED_CODEC':'STR','OPENVIDU_ALLOW_TRANSCODING':false,'OPENVIDU_RECORDING':false,'OPENVIDU_RECORDING_VERSION':'STR','OPENVIDU_RECORDING_PATH':'STR','OPENVIDU_RECORDING_PUBLIC_ACCESS':false,'OPENVIDU_RECORDING_NOTIFICATION':'STR',"
|
||||
+ "'OPENVIDU_RECORDING_CUSTOM_LAYOUT':'STR','OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT':0,'OPENVIDU_WEBHOOK':false,'OPENVIDU_WEBHOOK_ENDPOINT':'STR','OPENVIDU_WEBHOOK_HEADERS':[],"
|
||||
+ "'OPENVIDU_WEBHOOK_EVENTS':[]}");
|
||||
}
|
||||
|
@ -3885,6 +3887,57 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
checkNodeFetchChanged(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Force valid codec - Not Allow Transcoding")
|
||||
void forceValidCodecNotAllowTranscodingTest() throws Exception {
|
||||
log.info("Force codec - Force VP8 - Not Allow Transcoding");
|
||||
setupBrowser("chrome");
|
||||
this.forceCodecGenericE2eTest();
|
||||
this.user.getDriver().close();
|
||||
|
||||
log.info("Force codec Chrome - Force VP8 - Not Allow Transcoding");
|
||||
setupBrowser("chrome");
|
||||
this.forceCodecGenericE2eTest(VideoCodec.VP8, false);
|
||||
this.user.getDriver().close();
|
||||
|
||||
log.info("Force codec Chrome - Force H264 - Not Allow Transcoding");
|
||||
setupBrowser("chrome");
|
||||
this.forceCodecGenericE2eTest(VideoCodec.H264, false);
|
||||
this.user.getDriver().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Force valid codec - Allow Transcoding")
|
||||
void forceValidCodecAllowTranscodingTest() throws Exception {
|
||||
log.info("Force codec Chrome - Force VP8 - Allow Transcoding");
|
||||
setupBrowser("chrome");
|
||||
this.forceCodecGenericE2eTest(VideoCodec.VP8, true);
|
||||
this.user.getDriver().close();
|
||||
|
||||
log.info("Force codec Chrome - Force H264 - Allow Transcoding");
|
||||
setupBrowser("chrome");
|
||||
this.forceCodecGenericE2eTest(VideoCodec.H264, true);
|
||||
this.user.getDriver().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Force not valid codec - Not Allow Transcoding")
|
||||
void forceCodecNotValidCodecNotAllowTranscoding() throws Exception {
|
||||
// Start firefox with OpenH264 disabled to check not supported codecs
|
||||
log.info("Force codec Firefox - Force H264 - Allow Transcoding - Disabled H264 in Firefox");
|
||||
setupBrowser("firefoxDisabledOpenH264");
|
||||
this.forceCodecNotSupportedCodec(VideoCodec.H264, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Force not valid codec - Allow Transcoding")
|
||||
void forceCodecNotValidCodecAllowTranscoding() throws Exception {
|
||||
// Start firefox with OpenH264 disabled to check not supported codecs
|
||||
setupBrowser("firefoxDisabledOpenH264");
|
||||
log.info("Force codec Firefox - Force H264 - Allow Transcoding - Disabled H264 in Firefox");
|
||||
this.forceCodecNotSupportedCodec(VideoCodec.H264, true);
|
||||
}
|
||||
|
||||
private void checkNodeFetchChanged(boolean global, boolean hasChanged) {
|
||||
user.getDriver().findElement(By.id(global ? "list-sessions-btn" : "get-session-btn")).click();
|
||||
user.getWaiter().until(new NodeFetchHasChanged(hasChanged));
|
||||
|
@ -3905,4 +3958,148 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void forceCodecGenericE2eTest() throws Exception {
|
||||
forceCodecGenericE2eTest(null, null);
|
||||
}
|
||||
|
||||
private void forceCodecGenericE2eTest(VideoCodec codec, Boolean allowTranscoding) throws Exception {
|
||||
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
|
||||
JsonObject ovConfig = restClient.rest(HttpMethod.GET, "/openvidu/api/config", HttpStatus.SC_OK);
|
||||
VideoCodec defaultCodec = VideoCodec.valueOf(ovConfig.get("OPENVIDU_FORCED_CODEC").getAsString());
|
||||
Boolean defaultAllowTranscoding = ovConfig.get("OPENVIDU_ALLOW_TRANSCODING").getAsBoolean();
|
||||
|
||||
String sessionName = "CUSTOM_SESSION_" + ((codec != null) ? codec.name() : "DEFAULT_FORCE_CODEC");
|
||||
|
||||
// Configure Session to force Codec
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionName1 = user.getDriver().findElement(By.id("session-name-input-0"));
|
||||
sessionName1.clear();
|
||||
sessionName1.sendKeys(sessionName);
|
||||
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
|
||||
Thread.sleep(1000);
|
||||
|
||||
if (codec != null) {
|
||||
user.getDriver().findElement(By.id("forced-video-codec-select")).click();
|
||||
Thread.sleep(1000);
|
||||
user.getDriver().findElement(By.id("option-" + codec.name())).click();
|
||||
}
|
||||
if(allowTranscoding != null && allowTranscoding) {
|
||||
user.getDriver().findElement(By.id("allow-transcoding-checkbox")).click();
|
||||
}
|
||||
|
||||
user.getDriver().findElement(By.id("save-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Join Session
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionName2 = user.getDriver().findElement(By.id("session-name-input-1"));
|
||||
sessionName2.clear();
|
||||
sessionName2.sendKeys(sessionName);
|
||||
|
||||
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
|
||||
user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
|
||||
user.getEventManager().waitUntilEventReaches("streamCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
|
||||
|
||||
final int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size();
|
||||
Assert.assertEquals("Expected 4 videos but found " + numberOfVideos, 4, numberOfVideos);
|
||||
Assert.assertTrue("Videos were expected to have audio and video tracks", user.getEventManager()
|
||||
.assertMediaTracks(user.getDriver().findElements(By.tagName("video")), true, true));
|
||||
|
||||
// Check values
|
||||
JsonObject sessionJson = restClient.rest(HttpMethod.GET, "/openvidu/api/sessions/" + sessionName, HttpStatus.SC_OK);
|
||||
VideoCodec sessionCodec = VideoCodec.valueOf(sessionJson.get("forcedVideoCodec").getAsString());
|
||||
boolean sessionAllowTranscoding = sessionJson.get("allowTranscoding").getAsBoolean();
|
||||
|
||||
// Assert Selected Codec
|
||||
if (codec != null) {
|
||||
// If specified codec, assert selected codec
|
||||
Assert.assertEquals(sessionCodec, codec);
|
||||
} else {
|
||||
// If not specified, assert default codec
|
||||
Assert.assertEquals(sessionCodec, defaultCodec);
|
||||
}
|
||||
|
||||
// Assert Selected allow transcoding
|
||||
if (allowTranscoding != null) {
|
||||
Assert.assertEquals(sessionAllowTranscoding, allowTranscoding);
|
||||
} else {
|
||||
Assert.assertEquals(sessionAllowTranscoding, defaultAllowTranscoding);
|
||||
}
|
||||
|
||||
// Check real codecs
|
||||
VideoCodec codecToCheck = (codec != null) ? codec : defaultCodec;
|
||||
List<WebElement> statsButtons = user.getDriver().findElements(By.className("stats-button"));
|
||||
for(WebElement statButton: statsButtons) {
|
||||
statButton.click();
|
||||
Thread.sleep(1000);
|
||||
String videoCodecUsed = user.getDriver().findElement(By.id("video-codec-used")).getText();
|
||||
assertEquals(videoCodecUsed, "video/" + codecToCheck);
|
||||
user.getDriver().findElement(By.id("close-dialog-btn")).click();
|
||||
}
|
||||
|
||||
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/" + sessionName, HttpStatus.SC_NO_CONTENT);
|
||||
}
|
||||
|
||||
private void forceCodecNotSupportedCodec(VideoCodec codec, boolean allowTranscoding) throws Exception {
|
||||
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
|
||||
|
||||
String sessionName = "CUSTOM_SESSION_CODEC_NOT_SUPPORTED";
|
||||
|
||||
// Configure Session to force Codec
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionNameElem = user.getDriver().findElement(By.id("session-name-input-0"));
|
||||
sessionNameElem.clear();
|
||||
sessionNameElem.sendKeys(sessionName);
|
||||
user.getDriver().findElement(By.id("session-settings-btn-0")).click();
|
||||
Thread.sleep(1000);
|
||||
|
||||
user.getDriver().findElement(By.id("forced-video-codec-select")).click();
|
||||
Thread.sleep(1000);
|
||||
user.getDriver().findElement(By.id("option-" + codec.name())).click();
|
||||
|
||||
if(allowTranscoding) {
|
||||
user.getDriver().findElement(By.id("allow-transcoding-checkbox")).click();
|
||||
}
|
||||
|
||||
user.getDriver().findElement(By.id("save-btn")).click();
|
||||
Thread.sleep(1000);
|
||||
|
||||
if (allowTranscoding) {
|
||||
// If transcoding is enabled everything should work fine
|
||||
|
||||
// Join another user
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
WebElement sessionName2 = user.getDriver().findElement(By.id("session-name-input-1"));
|
||||
sessionName2.clear();
|
||||
sessionName2.sendKeys(sessionName);
|
||||
|
||||
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
|
||||
user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("accessAllowed", 2);
|
||||
user.getEventManager().waitUntilEventReaches("streamCreated", 4);
|
||||
user.getEventManager().waitUntilEventReaches("streamPlaying", 4);
|
||||
|
||||
final int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size();
|
||||
Assert.assertEquals("Expected 4 videos but found " + numberOfVideos, 4, numberOfVideos);
|
||||
Assert.assertTrue("Videos were expected to have audio and video tracks", user.getEventManager()
|
||||
.assertMediaTracks(user.getDriver().findElements(By.tagName("video")), true, true));
|
||||
|
||||
} else {
|
||||
// If transcoding not allowed it should return an alert with error
|
||||
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
|
||||
user.getWaiter().until(ExpectedConditions.alertIsPresent());
|
||||
Alert alert = user.getDriver().switchTo().alert();
|
||||
Assert.assertTrue("Alert does not contain expected text",
|
||||
alert.getText().contains("Error forcing codec: '" + codec.name() + "'"));
|
||||
alert.accept();
|
||||
}
|
||||
|
||||
|
||||
restClient.rest(HttpMethod.DELETE, "/openvidu/api/sessions/" + sessionName, HttpStatus.SC_NO_CONTENT);
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { EventsDialogComponent } from './components/dialogs/events-dialog/events
|
|||
import { PublisherPropertiesDialogComponent } from './components/dialogs/publisher-properties-dialog/publisher-properties-dialog.component';
|
||||
import { ScenarioPropertiesDialogComponent } from './components/dialogs/scenario-properties-dialog/scenario-properties-dialog.component';
|
||||
import { FilterDialogComponent } from './components/dialogs/filter-dialog/filter-dialog.component';
|
||||
import { ShowCodecDialogComponent } from './components/dialogs/show-codec-dialog/show-codec-dialog.component';
|
||||
|
||||
import { OpenviduRestService } from './services/openvidu-rest.service';
|
||||
import { OpenviduParamsService } from './services/openvidu-params.service';
|
||||
|
@ -48,6 +49,7 @@ import { MuteSubscribersService } from './services/mute-subscribers.service';
|
|||
PublisherPropertiesDialogComponent,
|
||||
ScenarioPropertiesDialogComponent,
|
||||
FilterDialogComponent,
|
||||
ShowCodecDialogComponent,
|
||||
UsersTableComponent,
|
||||
TableVideoComponent
|
||||
],
|
||||
|
@ -74,7 +76,8 @@ import { MuteSubscribersService } from './services/mute-subscribers.service';
|
|||
LocalRecordingDialogComponent,
|
||||
PublisherPropertiesDialogComponent,
|
||||
ScenarioPropertiesDialogComponent,
|
||||
FilterDialogComponent
|
||||
FilterDialogComponent,
|
||||
ShowCodecDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
|
|
@ -38,6 +38,10 @@ mat-radio-button:first-child {
|
|||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
#allow-transcoding-div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#record-div {
|
||||
padding-bottom: 20px;
|
||||
}
|
|
@ -39,6 +39,18 @@
|
|||
<input matInput placeholder="DefaultCustomLayout" type="text"
|
||||
[(ngModel)]="sessionProperties.defaultCustomLayout" id="default-custom-layout-input">
|
||||
</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>
|
||||
<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, ConnectionProperties } from 'openvidu-node-client';
|
||||
import { SessionProperties, MediaMode, Recording, RecordingMode, RecordingLayout, ConnectionProperties, VideoCodec } from 'openvidu-node-client';
|
||||
|
||||
@Component({
|
||||
selector: 'app-session-properties-dialog',
|
||||
|
@ -16,6 +16,7 @@ export class SessionPropertiesDialogComponent {
|
|||
customToken: string;
|
||||
forcePublishing: boolean = false;
|
||||
connectionProperties: ConnectionProperties;
|
||||
forceVideoCodec = VideoCodec;
|
||||
|
||||
filterName = 'GStreamerFilter';
|
||||
filters: string[] = [];
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'app-codec-used-dialog',
|
||||
template: `
|
||||
<div id="app-codec-dialog-container">
|
||||
<h2 id="codec-used-title-dialog" mat-dialog-title>Used Codec: <span id="video-codec-used">{{usedVideoCodec}}</span></h2>
|
||||
<button mat-button id="close-dialog-btn" [mat-dialog-close]="{}">CLOSE</button>
|
||||
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
#app-codec-dialog-container {
|
||||
text-align: center
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class ShowCodecDialogComponent {
|
||||
|
||||
usedVideoCodec;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<ShowCodecDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data) {
|
||||
this.usedVideoCodec = data.usedVideoCodec;
|
||||
}
|
||||
}
|
|
@ -93,7 +93,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
defaultOutputMode: Recording.OutputMode.COMPOSED,
|
||||
defaultRecordingLayout: RecordingLayout.BEST_FIT,
|
||||
defaultCustomLayout: '',
|
||||
customSessionId: ''
|
||||
customSessionId: '',
|
||||
forcedVideoCodec: null,
|
||||
allowTranscoding: null
|
||||
};
|
||||
|
||||
publisherProperties: PublisherProperties = {
|
||||
|
@ -521,7 +523,12 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.republishPossible = false;
|
||||
}).catch((error: OpenViduError) => {
|
||||
console.error(error);
|
||||
if (!error.name) {
|
||||
alert(error);
|
||||
} else {
|
||||
alert(error.name + ": " + error.message);
|
||||
}
|
||||
|
||||
this.republishPossible = true;
|
||||
this.session.unpublish(this.publisher);
|
||||
delete this.publisher;
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
<button class="video-btn events-btn bottom-left-rounded" title="Publisher events" (click)="openPublisherEventsDialog()">
|
||||
<mat-icon aria-label="Publisher events" class="mat-icon material-icons" role="img" aria-hidden="true">notifications</mat-icon>
|
||||
</button>
|
||||
<button class="video-btn stats-button bottom-left-rounded" title="Peer Connection Stats" (click)="showCodecUsed()">
|
||||
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bottom-div">
|
||||
<button class="video-btn pub-btn" title="Publish/Unpublish" (click)="pubUnpub()">
|
||||
|
@ -41,6 +44,9 @@
|
|||
<button *ngIf="subbed" class="video-btn events-btn bottom-left-rounded" title="Subscriber events" (click)="openSubscriberEventsDialog()">
|
||||
<mat-icon aria-label="Subscriber events" class="mat-icon material-icons" role="img" aria-hidden="true">notifications</mat-icon>
|
||||
</button>
|
||||
<button class="video-btn stats-button bottom-left-rounded" title="Peer Connection Stats" (click)="showCodecUsed()">
|
||||
<mat-icon aria-label="Peer Connection Stats" class="mat-icon material-icons" role="img" aria-hidden="true">info</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bottom-div">
|
||||
<button class="video-btn sub-btn" title="Subscribe/Unsubscribe" (click)="subUnsub()">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, Input, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material';
|
||||
import { ShowCodecDialogComponent } from '../dialogs/show-codec-dialog/show-codec-dialog.component';
|
||||
|
||||
import {
|
||||
StreamManager,
|
||||
|
@ -67,8 +68,10 @@ export class VideoComponent implements OnInit, OnDestroy {
|
|||
recordIcon = 'fiber_manual_record';
|
||||
pauseRecordIcon = '';
|
||||
|
||||
constructor(private dialog: MatDialog, private muteSubscribersService: MuteSubscribersService
|
||||
) { }
|
||||
// Stats
|
||||
usedVideoCodec: string;
|
||||
|
||||
constructor(private dialog: MatDialog, private muteSubscribersService: MuteSubscribersService) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
|
@ -727,4 +730,30 @@ export class VideoComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
async showCodecUsed() {
|
||||
let stats = await this.streamManager.stream.getWebRtcPeer().pc.getStats(null);
|
||||
let codecIdIndex = null;
|
||||
// Search codec Index
|
||||
stats.forEach(report => {
|
||||
console.log(report);
|
||||
if (!this.streamManager.remote && report.id.includes("RTCOutboundRTPVideoStream")) {
|
||||
codecIdIndex = report.codecId;
|
||||
|
||||
} else if (this.streamManager.remote && report.id.includes("RTCInboundRTPVideoStream")) {
|
||||
codecIdIndex = report.codecId;
|
||||
}
|
||||
})
|
||||
// Search codec Info
|
||||
stats.forEach(report => {
|
||||
if (report.id === codecIdIndex) {
|
||||
this.usedVideoCodec = report.mimeType;
|
||||
}
|
||||
})
|
||||
this.dialog.open(ShowCodecDialogComponent, {
|
||||
data: {
|
||||
usedVideoCodec: this.usedVideoCodec
|
||||
},
|
||||
width: '450px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue