mirror of https://github.com/OpenVidu/openvidu.git
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
parent
2d93abbd02
commit
8e5f5d4cf4
|
@ -109,6 +109,10 @@ export class OpenVidu {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
mediaServer: string;
|
mediaServer: string;
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
videoSimulcast: boolean;
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -257,6 +261,7 @@ export class OpenVidu {
|
||||||
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
|
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
|
||||||
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
|
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
|
||||||
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
|
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
|
||||||
|
videoSimulcast: properties.videoSimulcast,
|
||||||
filter: properties.filter
|
filter: properties.filter
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1540,6 +1540,7 @@ export class Session extends EventDispatcher {
|
||||||
this.openvidu.role = opts.role;
|
this.openvidu.role = opts.role;
|
||||||
this.openvidu.finalUserId = opts.finalUserId;
|
this.openvidu.finalUserId = opts.finalUserId;
|
||||||
this.openvidu.mediaServer = opts.mediaServer;
|
this.openvidu.mediaServer = opts.mediaServer;
|
||||||
|
this.openvidu.videoSimulcast = opts.videoSimulcast;
|
||||||
this.capabilities = {
|
this.capabilities = {
|
||||||
subscribe: true,
|
subscribe: true,
|
||||||
publish: this.openvidu.role !== 'SUBSCRIBER',
|
publish: this.openvidu.role !== 'SUBSCRIBER',
|
||||||
|
|
|
@ -938,7 +938,8 @@ export class Stream {
|
||||||
audio: this.hasAudio,
|
audio: this.hasAudio,
|
||||||
video: this.hasVideo,
|
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),
|
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)]) },
|
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||||
iceServers: this.getIceServersConf(),
|
iceServers: this.getIceServersConf(),
|
||||||
|
@ -946,6 +947,11 @@ export class Stream {
|
||||||
mediaServer: this.session.openvidu.mediaServer
|
mediaServer: this.session.openvidu.mediaServer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.session.openvidu.mediaServer !== 'mediasoup') {
|
||||||
|
// Simulcast is only supported by mediasoup
|
||||||
|
config.simulcast = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (reconnect) {
|
if (reconnect) {
|
||||||
this.disposeWebRtcPeer();
|
this.disposeWebRtcPeer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,5 +33,6 @@ export interface LocalConnectionOptions {
|
||||||
turnCredential: string;
|
turnCredential: string;
|
||||||
version: string;
|
version: string;
|
||||||
mediaServer: string;
|
mediaServer: string;
|
||||||
|
videoSimulcast: boolean;
|
||||||
life: number;
|
life: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,11 +71,4 @@ export interface OpenViduAdvancedConfiguration {
|
||||||
*/
|
*/
|
||||||
noStreamPlayingEventExceptionTimeout?: number;
|
noStreamPlayingEventExceptionTimeout?: number;
|
||||||
|
|
||||||
/**
|
}
|
||||||
* Whether to enable simulcast for Publishers or not.
|
|
||||||
*
|
|
||||||
* Default to `false`.
|
|
||||||
*/
|
|
||||||
enableSimulcastExperimental?: boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -82,6 +82,16 @@ export interface PublisherProperties {
|
||||||
*/
|
*/
|
||||||
videoSource?: string | MediaStreamTrack | boolean;
|
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
|
* **WARNING**: experimental option. This property may change in the near future
|
||||||
*
|
*
|
||||||
|
@ -89,4 +99,4 @@ export interface PublisherProperties {
|
||||||
*/
|
*/
|
||||||
filter?: Filter;
|
filter?: Filter;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,7 @@ public class ProtocolElements {
|
||||||
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
||||||
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
|
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
|
||||||
public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer";
|
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_RECORD_PARAM = "record";
|
||||||
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
||||||
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
||||||
|
|
|
@ -247,6 +247,16 @@ OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH=1000
|
||||||
# 0 means unconstrained
|
# 0 means unconstrained
|
||||||
OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
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
|
# 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
|
# when a codec can not be forced, transcoding will be allowed
|
||||||
# Values: VP8, VP9, H264, NONE
|
# Values: VP8, VP9, H264, NONE
|
||||||
|
|
|
@ -217,8 +217,13 @@ public class OpenviduConfig {
|
||||||
private boolean isTurnadminAvailable = false;
|
private boolean isTurnadminAvailable = false;
|
||||||
|
|
||||||
// Media Server properties
|
// Media Server properties
|
||||||
|
|
||||||
private MediaServer mediaServerInfo = MediaServer.kurento;
|
private MediaServer mediaServerInfo = MediaServer.kurento;
|
||||||
|
|
||||||
|
// Media properties
|
||||||
|
|
||||||
|
private boolean streamsVideoSimulcast = false;
|
||||||
|
|
||||||
// Plain config properties getters
|
// Plain config properties getters
|
||||||
|
|
||||||
public String getCoturnDatabaseDbname() {
|
public String getCoturnDatabaseDbname() {
|
||||||
|
@ -281,6 +286,10 @@ public class OpenviduConfig {
|
||||||
this.mediaServerInfo = mediaServerInfo;
|
this.mediaServerInfo = mediaServerInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStreamsVideoSimulcast() {
|
||||||
|
return this.streamsVideoSimulcast;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOpenViduRecordingPath() {
|
public String getOpenViduRecordingPath() {
|
||||||
return this.openviduRecordingPath;
|
return this.openviduRecordingPath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,19 @@ public class SessionEventsHandler {
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
|
||||||
this.openviduConfig.getMediaServer().name());
|
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) {
|
if (participant.getToken() != null) {
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
|
||||||
if (participant.getToken().getRole() != null) {
|
if (participant.getToken().getRole() != null) {
|
||||||
|
|
|
@ -114,6 +114,7 @@ public class ConfigRestController {
|
||||||
json.addProperty("OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH", openviduConfig.getVideoMinRecvBandwidth());
|
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_MAX_SEND_BANDWIDTH", openviduConfig.getVideoMaxSendBandwidth());
|
||||||
json.addProperty("OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH", openviduConfig.getVideoMinSendBandwidth());
|
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_FORCED_VIDEO_CODEC", openviduConfig.getOpenviduForcedCodec().name());
|
||||||
json.addProperty("OPENVIDU_STREAMS_ALLOW_TRANSCODING", openviduConfig.isOpenviduAllowingTranscoding());
|
json.addProperty("OPENVIDU_STREAMS_ALLOW_TRANSCODING", openviduConfig.isOpenviduAllowingTranscoding());
|
||||||
json.addProperty("OPENVIDU_SESSIONS_GARBAGE_INTERVAL", openviduConfig.getSessionGarbageInterval());
|
json.addProperty("OPENVIDU_SESSIONS_GARBAGE_INTERVAL", openviduConfig.getSessionGarbageInterval());
|
||||||
|
|
|
@ -147,6 +147,12 @@
|
||||||
"description": "Minimum video bandwidth sent from OpenVidu Server to clients, in kbps. 0 means unconstrained",
|
"description": "Minimum video bandwidth sent from OpenVidu Server to clients, in kbps. 0 means unconstrained",
|
||||||
"defaultValue": 300
|
"defaultValue": 300
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "OPENVIDU_STREAMS_VIDEO_SIMULCAST",
|
||||||
|
"type": "java.lang.Boolean",
|
||||||
|
"description": "Send Simulcast video.",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "OPENVIDU_STREAMS_FORCED_VIDEO_CODEC",
|
"name": "OPENVIDU_STREAMS_FORCED_VIDEO_CODEC",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
|
|
|
@ -42,6 +42,7 @@ OPENVIDU_STREAMS_VIDEO_MAX_RECV_BANDWIDTH=1000
|
||||||
OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH=300
|
OPENVIDU_STREAMS_VIDEO_MIN_RECV_BANDWIDTH=300
|
||||||
OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH=1000
|
OPENVIDU_STREAMS_VIDEO_MAX_SEND_BANDWIDTH=1000
|
||||||
OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
||||||
|
OPENVIDU_STREAMS_VIDEO_SIMULCAST=true
|
||||||
OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||||
OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<mat-checkbox [(ngModel)]="publisherProperties.publishAudio" (click)="publisherProperties.publishAudio = !publisherProperties.publishAudio">Publish audio</mat-checkbox>
|
<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.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.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-divider></mat-divider>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput placeholder="Audio source" [(ngModel)]="audioSource" [disabled]="(publisherProperties.audioSource === false)">
|
<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="close-btn" mat-button [mat-dialog-close]="initValue">CANCEL</button>
|
||||||
<button id="save-btn" mat-button [mat-dialog-close]="setCloseValue()">SAVE</button>
|
<button id="save-btn" mat-button [mat-dialog-close]="setCloseValue()">SAVE</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -121,7 +121,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
resolution: '640x480',
|
resolution: '640x480',
|
||||||
mirror: true,
|
mirror: true,
|
||||||
publishAudio: true,
|
publishAudio: true,
|
||||||
publishVideo: true
|
publishVideo: true,
|
||||||
|
videoSimulcast: true
|
||||||
};
|
};
|
||||||
|
|
||||||
publisherPropertiesAux: PublisherProperties;
|
publisherPropertiesAux: PublisherProperties;
|
||||||
|
@ -230,9 +231,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
this.OV = new OpenVidu();
|
this.OV = new OpenVidu();
|
||||||
|
|
||||||
const advancedConfiguration: OpenViduAdvancedConfiguration = {
|
const advancedConfiguration: OpenViduAdvancedConfiguration = {};
|
||||||
enableSimulcastExperimental: false
|
|
||||||
};
|
|
||||||
if (this.turnConf === 'freeice') {
|
if (this.turnConf === 'freeice') {
|
||||||
advancedConfiguration.iceServers = 'freeice';
|
advancedConfiguration.iceServers = 'freeice';
|
||||||
} else if (this.turnConf === 'manual') {
|
} else if (this.turnConf === 'manual') {
|
||||||
|
|
Loading…
Reference in New Issue