openvidu-server, browser: Add Simulcast Publisher config (#680)

Simulcast is a per-Publisher configuration that allows to enable
Simulcast senders on the client's PeerConnection of each sender.

Simulcast is a WebRTC feature that sends multiple simultaneous streams
with different video qualities, in order to let the media server decide
which quality is best for which Subscriber on the receiving side.

Enabled by default.
pull/681/head
Juan Navarro 2022-01-05 15:12:51 +01:00 committed by GitHub
parent 2d93abbd02
commit 8e5f5d4cf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 73 additions and 16 deletions

View File

@ -109,6 +109,10 @@ export class OpenVidu {
* @hidden
*/
mediaServer: string;
/**
* @hidden
*/
videoSimulcast: boolean;
/**
* @hidden
*/
@ -257,6 +261,7 @@ export class OpenVidu {
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
videoSimulcast: properties.videoSimulcast,
filter: properties.filter
};
} else {

View File

@ -1540,6 +1540,7 @@ export class Session extends EventDispatcher {
this.openvidu.role = opts.role;
this.openvidu.finalUserId = opts.finalUserId;
this.openvidu.mediaServer = opts.mediaServer;
this.openvidu.videoSimulcast = opts.videoSimulcast;
this.capabilities = {
subscribe: true,
publish: this.openvidu.role !== 'SUBSCRIBER',

View File

@ -938,7 +938,8 @@ export class Stream {
audio: this.hasAudio,
video: this.hasVideo,
},
simulcast: this.session.openvidu.advancedConfiguration.enableSimulcastExperimental || false,
simulcast:
this.outboundStreamOpts.publisherProperties.videoSimulcast ?? this.session.openvidu.videoSimulcast,
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
iceServers: this.getIceServersConf(),
@ -946,6 +947,11 @@ export class Stream {
mediaServer: this.session.openvidu.mediaServer
};
if (this.session.openvidu.mediaServer !== 'mediasoup') {
// Simulcast is only supported by mediasoup
config.simulcast = false;
}
if (reconnect) {
this.disposeWebRtcPeer();
}

View File

@ -33,5 +33,6 @@ export interface LocalConnectionOptions {
turnCredential: string;
version: string;
mediaServer: string;
videoSimulcast: boolean;
life: number;
}
}

View File

@ -71,11 +71,4 @@ export interface OpenViduAdvancedConfiguration {
*/
noStreamPlayingEventExceptionTimeout?: number;
/**
* Whether to enable simulcast for Publishers or not.
*
* Default to `false`.
*/
enableSimulcastExperimental?: boolean;
}
}

View File

@ -82,6 +82,16 @@ export interface PublisherProperties {
*/
videoSource?: string | MediaStreamTrack | boolean;
/**
* Send Simulcast video.
* Publishers will encode duplicate video streams with different qualities,
* so the media server is able to select the most appropriate quality stream
* for each Subscriber.
* This setting is honored only if OpenVidu Server was configured to use the
* mediasoup media server. Otherwise, Simulcast will be disabled.
*/
videoSimulcast?: boolean;
/**
* **WARNING**: experimental option. This property may change in the near future
*
@ -89,4 +99,4 @@ export interface PublisherProperties {
*/
filter?: Filter;
}
}

View File

@ -160,6 +160,7 @@ public class ProtocolElements {
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer";
public static final String PARTICIPANTJOINED_SIMULCAST_PARAM = "videoSimulcast";
public static final String PARTICIPANTJOINED_RECORD_PARAM = "record";
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";

View File

@ -247,6 +247,16 @@ OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH=1000
# 0 means unconstrained
OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# Send Simulcast video.
# Publishers will encode duplicate video streams with different qualities,
# so the media server is able to select the most appropriate quality stream
# for each Subscriber.
# This setting is honored only if OpenVidu Server was configured to use the
# mediasoup media server. Otherwise, Simulcast will be disabled.
# Values: true | false
# Default: true
#OPENVIDU_STREAMS_VIDEO_SIMULCAST=true
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
# when a codec can not be forced, transcoding will be allowed
# Values: VP8, VP9, H264, NONE

View File

@ -217,8 +217,13 @@ public class OpenviduConfig {
private boolean isTurnadminAvailable = false;
// Media Server properties
private MediaServer mediaServerInfo = MediaServer.kurento;
// Media properties
private boolean streamsVideoSimulcast = false;
// Plain config properties getters
public String getCoturnDatabaseDbname() {
@ -281,6 +286,10 @@ public class OpenviduConfig {
this.mediaServerInfo = mediaServerInfo;
}
public boolean isStreamsVideoSimulcast() {
return this.streamsVideoSimulcast;
}
public String getOpenViduRecordingPath() {
return this.openviduRecordingPath;
}

View File

@ -165,6 +165,19 @@ public class SessionEventsHandler {
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
this.openviduConfig.getMediaServer().name());
switch (this.openviduConfig.getMediaServer()) {
case mediasoup:
// mediasoup supports simulcast
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SIMULCAST_PARAM,
this.openviduConfig.isStreamsVideoSimulcast());
break;
case kurento:
default:
// Kurento does not support simulcast
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SIMULCAST_PARAM, false);
break;
}
if (participant.getToken() != null) {
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
if (participant.getToken().getRole() != null) {

View File

@ -114,6 +114,7 @@ public class ConfigRestController {
json.addProperty("OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH", openviduConfig.getVideoMinRecvBandwidth());
json.addProperty("OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH", openviduConfig.getVideoMaxSendBandwidth());
json.addProperty("OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH", openviduConfig.getVideoMinSendBandwidth());
json.addProperty("OPENVIDU_STREAMS_VIDEO_SIMULCAST", openviduConfig.isStreamsVideoSimulcast());
json.addProperty("OPENVIDU_STREAMS_FORCED_VIDEO_CODEC", openviduConfig.getOpenviduForcedCodec().name());
json.addProperty("OPENVIDU_STREAMS_ALLOW_TRANSCODING", openviduConfig.isOpenviduAllowingTranscoding());
json.addProperty("OPENVIDU_SESSIONS_GARBAGE_INTERVAL", openviduConfig.getSessionGarbageInterval());

View File

@ -147,6 +147,12 @@
"description": "Minimum video bandwidth sent from OpenVidu Server to clients, in kbps. 0 means unconstrained",
"defaultValue": 300
},
{
"name": "OPENVIDU_STREAMS_VIDEO_SIMULCAST",
"type": "java.lang.Boolean",
"description": "Send Simulcast video.",
"defaultValue": true
},
{
"name": "OPENVIDU_STREAMS_FORCED_VIDEO_CODEC",
"type": "java.lang.String",

View File

@ -42,6 +42,7 @@ OPENVIDU_STREAMS_VIDEO_MAX_RECV_BANDWIDTH=1000
OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH=300
OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH=1000
OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
OPENVIDU_STREAMS_VIDEO_SIMULCAST=true
OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
OPENVIDU_STREAMS_ALLOW_TRANSCODING=false

View File

@ -8,6 +8,7 @@
<mat-checkbox [(ngModel)]="publisherProperties.publishAudio" (click)="publisherProperties.publishAudio = !publisherProperties.publishAudio">Publish audio</mat-checkbox>
<mat-checkbox [(ngModel)]="publisherProperties.publishVideo" (click)="publisherProperties.publishVideo = !publisherProperties.publishVideo">Publish video</mat-checkbox>
<mat-checkbox [(ngModel)]="publisherProperties.mirror" (click)="publisherProperties.mirror = !publisherProperties.mirror">Mirror</mat-checkbox>
<mat-checkbox [(ngModel)]="publisherProperties.videoSimulcast" (click)="publisherProperties.videoSimulcast = !publisherProperties.videoSimulcast">Video Simulcast</mat-checkbox>
<mat-divider></mat-divider>
<mat-form-field>
<input matInput placeholder="Audio source" [(ngModel)]="audioSource" [disabled]="(publisherProperties.audioSource === false)">
@ -52,4 +53,4 @@
<button id="close-btn" mat-button [mat-dialog-close]="initValue">CANCEL</button>
<button id="save-btn" mat-button [mat-dialog-close]="setCloseValue()">SAVE</button>
</mat-dialog-actions>
</div>
</div>

View File

@ -121,7 +121,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
resolution: '640x480',
mirror: true,
publishAudio: true,
publishVideo: true
publishVideo: true,
videoSimulcast: true
};
publisherPropertiesAux: PublisherProperties;
@ -230,9 +231,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.OV = new OpenVidu();
const advancedConfiguration: OpenViduAdvancedConfiguration = {
enableSimulcastExperimental: false
};
const advancedConfiguration: OpenViduAdvancedConfiguration = {};
if (this.turnConf === 'freeice') {
advancedConfiguration.iceServers = 'freeice';
} else if (this.turnConf === 'manual') {