Merge remote-tracking branch 'upstream/master'

pull/88/head
techcouncilbox 2018-07-10 09:09:39 +02:00
commit 336eff2b91
38 changed files with 727 additions and 384 deletions

View File

@ -76,7 +76,8 @@ export class Publisher extends StreamManager {
this.properties = properties;
this.openvidu = openvidu;
this.stream.ee.on('local-stream-destroyed-by-disconnect', (reason: string) => {
this.stream.ee.on('local-stream-destroyed', (reason: string) => {
this.stream.isLocalStreamPublished = false;
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
this.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();

View File

@ -330,7 +330,7 @@ export class Session implements EventDispatcher {
publisher.session = this;
publisher.stream.session = this;
if (!publisher.stream.isLocalStreamPublished) {
if (!publisher.stream.publishedOnce) {
// 'Session.unpublish(Publisher)' has NOT been called
this.connection.addStream(publisher.stream);
publisher.stream.publish()
@ -413,6 +413,88 @@ export class Session implements EventDispatcher {
}
/**
* Forces some user to leave the session
*
* #### Events dispatched
*
* The behavior is the same as when some user calls [[Session.disconnect]], but `reason` property in all events will be `"forceDisconnectByUser"`.
*
* The local [[Session]] object will dispatch:
* - A `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"`
* - A `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"`
*
* The remote [[Session]] object of every other participant will dispatch:
* - A `streamDestroyed` event if the evicted user was publishing a stream, with property `reason` set to `"forceDisconnectByUser"`
* - A `connectionDestroyed` event for the evicted user, with property `reason` set to `"forceDisconnectByUser"`
*
* If any, the [[Publisher]] object of the evicted participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not
*/
forceDisconnect(connection: Connection): Promise<any> {
return new Promise((resolve, reject) => {
console.info('Forcing disconnect for connection ' + connection.connectionId);
this.openvidu.sendRequest(
'forceDisconnect',
{ connectionId: connection.connectionId },
(error, response) => {
if (error) {
console.error('Error forcing disconnect for Connection ' + connection.connectionId, error);
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to force a disconnection"));
} else {
reject(error);
}
} else {
console.info('Forcing disconnect correctly for Connection ' + connection.connectionId);
resolve();
}
}
);
});
}
/**
* Forces some user to unpublish a Stream
*
* #### Events dispatched
*
* The behavior is the same as when some user calls [[Session.unpublish]], but `reason` property in all events will be `"forceUnpublishByUser"`.
*
* The local [[Session]] object will dispatch a `streamDestroyed` event with property `reason` set to `"forceUnpublishByUser"`
*
* The remote [[Session]] object of every other participant will dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
*
* The [[Publisher]] object of the affected participant will also dispatch a `streamDestroyed` event with property `reason` set to `"forceDisconnectByUser"`
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not
*/
forceUnpublish(stream: Stream): Promise<any> {
return new Promise((resolve, reject) => {
console.info('Forcing unpublish for stream ' + stream.streamId);
this.openvidu.sendRequest(
'forceUnpublish',
{ streamId: stream.streamId },
(error, response) => {
if (error) {
console.error('Error forcing unpublish for Stream ' + stream.streamId, error);
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to force an unpublishing"));
} else {
reject(error);
}
} else {
console.info('Forcing unpublish correctly for Stream ' + stream.streamId);
resolve();
}
}
);
});
}
/**
* Sends one signal. `signal` object has the following optional properties:
* ```json
@ -630,23 +712,28 @@ export class Session implements EventDispatcher {
* @hidden
*/
onParticipantUnpublished(msg): void {
this.getRemoteConnection(msg.connectionId, "Remote connection '" + msg.connectionId + "' unknown when 'onParticipantUnpublished'. " +
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
if (msg.connectionId === this.connection.connectionId) {
// Your stream has been forcedly unpublished from the session
this.stopPublisherStream(msg.reason);
} else {
this.getRemoteConnection(msg.connectionId, "Remote connection '" + msg.connectionId + "' unknown when 'onParticipantUnpublished'. " +
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(connection => {
.then(connection => {
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream, msg.reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', connection.stream, msg.reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
// Deleting the remote stream
const streamId: string = connection.stream.streamId;
delete this.remoteStreamsCreated[streamId];
connection.removeStream(streamId);
})
.catch(openViduError => {
console.error(openViduError);
});
// Deleting the remote stream
const streamId: string = connection.stream.streamId;
delete this.remoteStreamsCreated[streamId];
connection.removeStream(streamId);
})
.catch(openViduError => {
console.error(openViduError);
});
}
}
/**
@ -658,27 +745,6 @@ export class Session implements EventDispatcher {
if (!!this.sessionId && !this.connection.disposed) {
this.leave(true, msg.reason);
}
} else {
// Other user has been evicted from the session
this.getRemoteConnection(msg.connectionId, 'Remote connection ' + msg.connectionId + " unknown when 'onParticipantEvicted'. " +
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(connection => {
if (!!connection.stream) {
const stream = connection.stream;
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', stream, msg.reason);
this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
delete this.remoteStreamsCreated[stream.streamId];
}
delete this.remoteConnections[connection.connectionId];
this.ee.emitEvent('connectionDestroyed', [new ConnectionEvent(false, this, 'connectionDestroyed', connection, msg.reason)]);
})
.catch(openViduError => {
console.error(openViduError);
});
}
}
@ -860,14 +926,7 @@ export class Session implements EventDispatcher {
this.openvidu.closeWs();
}
if (!!this.connection.stream) {
// Dispose Publisher's local stream
this.connection.stream.disposeWebRtcPeer();
if (this.connection.stream.isLocalStreamPublished) {
// Make Publisher object dispatch 'streamDestroyed' event if the Stream was published
this.connection.stream.ee.emitEvent('local-stream-destroyed-by-disconnect', [reason]);
}
}
this.stopPublisherStream(reason);
if (!this.connection.disposed) {
// Make Session object dispatch 'sessionDisconnected' event (if it is not already disposed)
@ -906,7 +965,9 @@ export class Session implements EventDispatcher {
// Initialize capabilities object with the role
this.capabilities = {
subscribe: true,
publish: this.openvidu.role !== 'SUBSCRIBER'
publish: this.openvidu.role !== 'SUBSCRIBER',
forceUnpublish: this.openvidu.role === 'MODERATOR',
forceDisconnect: this.openvidu.role === 'MODERATOR'
};
// Initialize local Connection object with values returned by openvidu-server
@ -951,6 +1012,17 @@ export class Session implements EventDispatcher {
});
}
private stopPublisherStream(reason: string) {
if (!!this.connection.stream) {
// Dispose Publisher's local stream
this.connection.stream.disposeWebRtcPeer();
if (this.connection.stream.isLocalStreamPublished) {
// Make Publisher object dispatch 'streamDestroyed' event if the Stream was published
this.connection.stream.ee.emitEvent('local-stream-destroyed', [reason]);
}
}
}
private stringClientMetadata(metadata: any): string {
if (typeof metadata !== 'string') {
return JSON.stringify(metadata);

View File

@ -26,6 +26,7 @@ import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpea
import EventEmitter = require('wolfy87-eventemitter');
import hark = require('hark');
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
/**
@ -118,6 +119,10 @@ export class Stream {
* @hidden
*/
isLocalStreamPublished = false;
/**
* @hidden
*/
publishedOnce = false;
/**
* @hidden
*/
@ -465,12 +470,17 @@ export class Stream {
videoDimensions: JSON.stringify(this.videoDimensions)
}, (error, response) => {
if (error) {
reject('Error on publishVideo: ' + JSON.stringify(error));
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
} else {
reject('Error on publishVideo: ' + JSON.stringify(error));
}
} else {
this.webRtcPeer.processAnswer(response.sdpAnswer)
.then(() => {
this.streamId = response.id;
this.isLocalStreamPublished = true;
this.publishedOnce = true;
if (this.displayMyRemote()) {
this.remotePeerSuccessfullyEstablished();
}

View File

@ -82,7 +82,8 @@ export enum OpenViduErrorName {
PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR',
/**
* _Not in use yet_
* The client tried to call a method without the required permissions. This can occur for methods [[Session.publish]],
* [[Session.forceUnpublish]] and [[Session.forceDisconnect]]
*/
OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED',

View File

@ -35,6 +35,8 @@ export class ConnectionEvent extends Event {
/**
* For 'connectionDestroyed' event:
* - "disconnect": the remote user has called `Session.disconnect()`
* - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
* - "networkDisconnect": the remote user network connection has dropped
*
* For 'connectionCreated' empty string

View File

@ -34,8 +34,8 @@ export class RecordingEvent extends Event {
/**
* The recording name you supplied to openvidu-server. For example, to name your recording file MY_RECORDING:
* - With **API REST**: POST to `/api/recordings/start` passing JSON body `{"session":"sessionId","name":"MY_RECORDING"}`
* - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, MY_RECORDING)` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name(MY_RECORDING).build())`
* - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, MY_RECORDING)` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name(MY_RECORDING).build())`
* - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name("MY_RECORDING").build())`
* - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, {name: "MY_RECORDING"})`
*
* If no name is supplied, this property will be undefined and the recorded file will be named after property [[id]]
*/

View File

@ -26,6 +26,9 @@ export class SessionDisconnectedEvent extends Event {
/**
* - "disconnect": you have called `Session.disconnect()`
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": you have been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": your network connection has dropped
*/
reason: string;

View File

@ -37,6 +37,11 @@ export class StreamEvent extends Event {
* For 'streamDestroyed' event:
* - "unpublish": method `Session.unpublish()` has been called
* - "disconnect": method `Session.disconnect()` has been called
* - "forceUnpublishByUser": some user has called `Session.forceUnpublish()` over the Stream
* - "forceDisconnectByUser": some user has called `Session.forceDisconnect()` over the Stream
* - "forceUnpublishByServer": the user's stream has been unpublished from the Session by the application
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the user's network connection has dropped
*
* For 'streamCreated' empty string

View File

@ -20,6 +20,16 @@
*/
export interface Capabilities {
/**
* `true` if the client can call [[Session.forceDisconnect]], `false` if not
*/
forceDisconnect: boolean;
/**
* `true` if the client can call [[Session.forceUnpublish]], `false` if not
*/
forceUnpublish: boolean;
/**
* `true` if the client can call [[Session.publish]], `false` if not
*/

View File

@ -35,22 +35,22 @@ export interface StreamManagerVideo {
/**
* The DOM HTMLElement assigned as target element when creating a video for the StreamManager. This property is defined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter.
* - [[SessionManager.createVideoElement]] has been called.
* - [[StreamManager.createVideoElement]] has been called.
*
* This property is undefined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - [[SessionManager.addVideoElement]] has been called.
* - [[StreamManager.addVideoElement]] has been called.
*/
targetElement?: HTMLElement;
/**
* How the DOM video element should be inserted with respect to `targetElement`. This property is defined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter.
* - [[SessionManager.createVideoElement]] has been called.
* - [[StreamManager.createVideoElement]] has been called.
*
* This property is undefined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - [[SessionManager.addVideoElement]] has been called.
* - [[StreamManager.addVideoElement]] has been called.
*/
insertMode?: VideoInsertMode;

View File

@ -87,7 +87,13 @@ public class ProtocolElements {
public static final String STREAMPROPERTYCHANGED_PROPERTY_PARAM = "property";
public static final String STREAMPROPERTYCHANGED_NEWVALUE_PARAM = "newValue";
public static final String STREAMPROPERTYCHANGED_REASON_PARAM = "reason";
public static final String FORCEDISCONNECT_METHOD = "forceDisconnect";
public static final String FORCEDISCONNECT_CONNECTIONID_PARAM = "connectionId";
public static final String FORCEUNPUBLISH_METHOD = "forceUnpublish";
public static final String FORCEUNPUBLISH_STREAMID_PARAM = "streamId";
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
public static final String PARTICIPANTJOINED_METHOD = "participantJoined";

View File

@ -20,19 +20,18 @@ package io.openvidu.java.client;
public enum OpenViduRole {
/**
* Can subscribe to published streams of other users
* Can subscribe to published Streams of other users
*/
SUBSCRIBER,
/**
* SUBSCRIBER permissions + can publish their own streams
* SUBSCRIBER permissions + can publish their own Streams (call <code>Session.publish()</code>)
*/
PUBLISHER,
/**
* <i>(not available yet)</i> SUBSCRIBER and PUBLIHSER permissions + can force
* <code>unpublish()</code> and <code>disconnect()</code> over a third-party
* stream or user
* SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection
* (call <code>Session.forceUnpublish()</code> and <code>Session.forceDisconnect()</code>)
*/
MODERATOR;
}

View File

@ -18,17 +18,18 @@
export enum OpenViduRole {
/**
* Can subscribe to published streams of other users
* Can subscribe to published Streams of other users
*/
SUBSCRIBER = 'SUBSCRIBER',
/**
* SUBSCRIBER permissions + can publish their own streams
* SUBSCRIBER permissions + can publish their own Streams (call `Session.publish()`)
*/
PUBLISHER = 'PUBLISHER',
/**
* _(not available yet)_ SUBSCRIBER + PUBLIHSER permissions + can force `unpublish()` and `disconnect()` over a third-party stream or user
* SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection
* (call `Session.forceUnpublish()` and `Session.forceDisconnect()`)
*/
MODERATOR = 'MODERATOR'
}

View File

@ -25,10 +25,10 @@ export interface TokenOptions {
* - If you have provided some data when calling `Session.connect(TOKEN, DATA)` (`DATA` defined), then `Connection.data` will have the following structure: `"CLIENT_DATA%/%SERVER_DATA"`, being `CLIENT_DATA` the second
* parameter passed in OpenVidu Browser in method `Session.connect` and `SERVER_DATA` this [[TokenOptions.data]] property.
*/
data: string;
data?: string;
/**
* The role assigned to this token
*/
role: OpenViduRole;
role?: OpenViduRole;
}

View File

@ -48,6 +48,7 @@ import com.google.gson.JsonParser;
import io.openvidu.server.cdr.CDRLoggerFile;
import io.openvidu.server.cdr.CallDetailRecord;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.SessionEventsHandler;
import io.openvidu.server.core.SessionManager;
import io.openvidu.server.coturn.CoturnCredentialsService;
import io.openvidu.server.coturn.CoturnCredentialsServiceFactory;
@ -126,7 +127,7 @@ public class OpenViduServer implements JsonRpcConfigurer {
@Bean
@ConditionalOnMissingBean
public KurentoSessionEventsHandler kurentoSessionEventsHandler() {
public SessionEventsHandler sessionEventsHandler() {
return new KurentoSessionEventsHandler();
}

View File

@ -44,19 +44,28 @@ public class OpenviduConfig {
private String openviduRecordingPath;
@Value("${openvidu.recording.public-access}")
boolean openviduRecordingPublicAccess;
private boolean openviduRecordingPublicAccess;
@Value("${openvidu.recording.notification}")
String openviduRecordingNotification;
private String openviduRecordingNotification;
@Value("${openvidu.recording.custom-layout}")
String openviduRecordingCustomLayout;
private String openviduRecordingCustomLayout;
@Value("${openvidu.recording.version}")
String openviduRecordingVersion;
private String openviduRecordingVersion;
@Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}")
private String springProfile;
@Value("${openvidu.streams.video.max-recv-bandwidth}")
private int openviduStreamsVideoMaxRecvBandwidth;
@Value("${openvidu.streams.video.min-recv-bandwidth}")
private int openviduStreamsVideoMinRecvBandwidth;
@Value("${openvidu.streams.video.max-send-bandwidth}")
private int openviduStreamsVideoMaxSendBandwidth;
@Value("${openvidu.streams.video.min-send-bandwidth}")
private int openviduStreamsVideoMinSendBandwidth;
@Value("${coturn.redis.ip}")
private String coturnRedisIp;
@ -70,6 +79,9 @@ public class OpenviduConfig {
@Value("${coturn.redis.connect-timeout}")
private String coturnRedisConnectTimeout;
@Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}")
private String springProfile;
private String finalUrl;
public String getOpenViduPublicUrl() {
@ -132,11 +144,27 @@ public class OpenviduConfig {
return springProfile;
}
public int getVideoMaxRecvBandwidth() {
return this.openviduStreamsVideoMaxRecvBandwidth;
}
public int getVideoMinRecvBandwidth() {
return this.openviduStreamsVideoMinRecvBandwidth;
}
public int getVideoMaxSendBandwidth() {
return this.openviduStreamsVideoMaxSendBandwidth;
}
public int getVideoMinSendBandwidth() {
return this.openviduStreamsVideoMinSendBandwidth;
}
public String getCoturnDatabaseString() {
return "\"ip=" + this.coturnRedisIp + " dbname=" + this.coturnRedisDbname + " password="
+ this.coturnRedisPassword + " connect_timeout=" + this.coturnRedisConnectTimeout + "\"";
}
public String getCoturnDatabaseDbname() {
return this.coturnRedisDbname;
}
@ -147,6 +175,9 @@ public class OpenviduConfig {
case "none":
roles = new ParticipantRole[0];
break;
case "moderator":
roles = new ParticipantRole[] { ParticipantRole.MODERATOR };
break;
case "publisher_moderator":
roles = new ParticipantRole[] { ParticipantRole.PUBLISHER, ParticipantRole.MODERATOR };
break;

View File

@ -93,7 +93,7 @@ public class Participant {
this.streaming = streaming;
}
public String getPublisherStremId() {
public String getPublisherStreamId() {
return null;
}

View File

@ -26,13 +26,13 @@ import io.openvidu.java.client.SessionProperties;
public interface Session {
String getSessionId();
SessionProperties getSessionProperties();
void join(Participant participant);
void leave(String participantPrivateId, String reason);
boolean close(String reason);
boolean isClosed();
@ -44,9 +44,9 @@ public interface Session {
Participant getParticipantByPublicId(String participantPublicId);
int getActivePublishers();
JSONObject toJSON();
JSONObject withStatsToJSON();
}

View File

@ -96,7 +96,7 @@ public class SessionEventsHandler {
JsonObject stream = new JsonObject();
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMID_PARAM,
existingParticipant.getPublisherStremId());
existingParticipant.getPublisherStreamId());
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMHASAUDIO_PARAM,
kParticipant.getPublisherMediaOptions().hasAudio);
stream.addProperty(ProtocolElements.JOINROOM_PEERSTREAMHASVIDEO_PARAM,
@ -214,13 +214,19 @@ public class SessionEventsHandler {
}
}
public void onUnpublishMedia(Participant participant, Set<Participant> participants, Integer transactionId,
OpenViduException error, String reason) {
if (error != null) {
rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error);
return;
public void onUnpublishMedia(Participant participant, Set<Participant> participants, Participant moderator,
Integer transactionId, OpenViduException error, String reason) {
boolean isRpcFromModerator = transactionId != null && moderator != null;
boolean isRpcFromOwner = transactionId != null && moderator == null;
if (isRpcFromModerator) {
if (error != null) {
rpcNotificationService.sendErrorResponse(moderator.getParticipantPrivateId(), transactionId, null,
error);
return;
}
rpcNotificationService.sendResponse(moderator.getParticipantPrivateId(), transactionId, new JsonObject());
}
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
JsonObject params = new JsonObject();
params.addProperty(ProtocolElements.PARTICIPANTUNPUBLISHED_NAME_PARAM, participant.getParticipantPublicId());
@ -228,16 +234,28 @@ public class SessionEventsHandler {
for (Participant p : participants) {
if (p.getParticipantPrivateId().equals(participant.getParticipantPrivateId())) {
continue;
if (!isRpcFromOwner) {
rpcNotificationService.sendNotification(p.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD, params);
} else {
if (error != null) {
rpcNotificationService.sendErrorResponse(p.getParticipantPrivateId(), transactionId, null,
error);
return;
}
rpcNotificationService.sendResponse(p.getParticipantPrivateId(), transactionId, new JsonObject());
}
} else {
rpcNotificationService.sendNotification(p.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD, params);
if (error == null) {
rpcNotificationService.sendNotification(p.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD, params);
}
}
}
}
public void onSubscribe(Participant participant, Session session, String senderName, String sdpAnswer,
Integer transactionId, OpenViduException error) {
public void onSubscribe(Participant participant, Session session, String sdpAnswer, Integer transactionId,
OpenViduException error) {
if (error != null) {
rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error);
return;
@ -260,8 +278,7 @@ public class SessionEventsHandler {
}
}
public void onUnsubscribe(Participant participant, String senderName, Integer transactionId,
OpenViduException error) {
public void onUnsubscribe(Participant participant, Integer transactionId, OpenViduException error) {
if (error != null) {
rpcNotificationService.sendErrorResponse(participant.getParticipantPrivateId(), transactionId, null, error);
return;
@ -349,12 +366,30 @@ public class SessionEventsHandler {
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
}
public void onParticipantEvicted(Participant participant, String reason) {
public void onForceDisconnect(Participant moderator, Participant evictedParticipant, Set<Participant> participants,
Integer transactionId, OpenViduException error, String reason) {
boolean isRpcCall = transactionId != null;
if (isRpcCall) {
if (error != null) {
rpcNotificationService.sendErrorResponse(moderator.getParticipantPrivateId(), transactionId, null,
error);
return;
}
rpcNotificationService.sendResponse(moderator.getParticipantPrivateId(), transactionId, new JsonObject());
}
JsonObject params = new JsonObject();
params.addProperty(ProtocolElements.PARTICIPANTEVICTED_CONNECTIONID_PARAM, participant.getParticipantPublicId());
params.addProperty(ProtocolElements.PARTICIPANTEVICTED_CONNECTIONID_PARAM,
evictedParticipant.getParticipantPublicId());
params.addProperty(ProtocolElements.PARTICIPANTEVICTED_REASON_PARAM, reason);
rpcNotificationService.sendNotification(participant.getParticipantPrivateId(),
rpcNotificationService.sendNotification(evictedParticipant.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTEVICTED_METHOD, params);
for (Participant p : participants) {
rpcNotificationService.sendNotification(p.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTEVICTED_METHOD, params);
}
}
public void sendRecordingStartedNotification(Session session, Recording recording) {

View File

@ -22,7 +22,6 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
@ -80,7 +79,7 @@ public abstract class SessionManager {
public abstract void publishVideo(Participant participant, MediaOptions mediaOptions, Integer transactionId);
public abstract void unpublishVideo(Participant participant, Integer transactionId, String reason);
public abstract void unpublishVideo(Participant participant, Participant moderator, Integer transactionId, String reason);
public abstract void subscribe(Participant participant, String senderName, String sdpOffer, Integer transactionId);
@ -94,15 +93,10 @@ public abstract class SessionManager {
public abstract void onIceCandidate(Participant participant, String endpointName, String candidate,
int sdpMLineIndex, String sdpMid, Integer transactionId);
/**
* Application-originated request to remove a participant from a session. <br/>
* <strong>Side effects:</strong> The session event handler should notify the
* participant that she has been evicted. Should also send notifications to all
* other participants about the one that's just been evicted.
*
*/
public void evictParticipant(String participantPrivateId, String reason) throws OpenViduException {
}
public abstract boolean unpublishStream(Session session, String streamId, Participant moderator, Integer transactionId, String reason);
public abstract void evictParticipant(Participant evictedParticipant, Participant moderator, Integer transactionId,
String reason);
/**
* Returns a Session given its id
@ -285,6 +279,18 @@ public abstract class SessionManager {
}
}
public boolean isModeratorInSession(String sessionId, Participant participant) {
if (!this.isInsecureParticipant(participant.getParticipantPrivateId())) {
if (this.sessionidParticipantpublicidParticipant.get(sessionId) != null) {
return ParticipantRole.MODERATOR.equals(participant.getToken().getRole());
} else {
return false;
}
} else {
return true;
}
}
public boolean isInsecureParticipant(String participantPrivateId) {
if (this.insecureUsers.containsKey(participantPrivateId)) {
log.info("The user with private id {} is an INSECURE user", participantPrivateId);
@ -407,13 +413,11 @@ public abstract class SessionManager {
throw new OpenViduException(Code.ROOM_CLOSED_ERROR_CODE, "Session '" + sessionId + "' already closed");
}
Set<Participant> participants = getParticipants(sessionId);
// copy the ids as they will be removed from the map
Set<String> pids = participants.stream().map(Participant::getParticipantPrivateId).collect(Collectors.toSet());
for (String pid : pids) {
for (Participant p : participants) {
try {
this.evictParticipant(pid, reason);
this.evictParticipant(p, null, null, reason);
} catch (OpenViduException e) {
log.warn("Error evicting participant with id '{}' from session '{}'", pid, sessionId, e);
log.warn("Error evicting participant '{}' from session '{}'", p.getParticipantPublicId(), sessionId, e);
}
}

View File

@ -17,6 +17,6 @@
package io.openvidu.server.kurento;
public enum MutedMediaType {
public enum TrackType {
ALL, VIDEO, AUDIO;
}

View File

@ -39,7 +39,6 @@ import org.kurento.client.SdpEndpoint;
import org.kurento.client.internal.server.KurentoServerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
@ -49,7 +48,7 @@ import io.openvidu.server.config.InfoHandler;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant;
import io.openvidu.server.kurento.MutedMediaType;
import io.openvidu.server.kurento.TrackType;
import io.openvidu.server.kurento.endpoint.KmsEvent;
import io.openvidu.server.kurento.endpoint.MediaEndpoint;
import io.openvidu.server.kurento.endpoint.PublisherEndpoint;
@ -60,8 +59,7 @@ public class KurentoParticipant extends Participant {
private static final Logger log = LoggerFactory.getLogger(KurentoParticipant.class);
@Autowired
protected OpenviduConfig openviduConfig;
private OpenviduConfig openviduConfig;
private InfoHandler infoHandler;
private CallDetailRecord CDR;
@ -78,12 +76,14 @@ public class KurentoParticipant extends Participant {
private final ConcurrentMap<String, SubscriberEndpoint> subscribers = new ConcurrentHashMap<String, SubscriberEndpoint>();
public KurentoParticipant(Participant participant, KurentoSession kurentoSession, MediaPipeline pipeline,
InfoHandler infoHandler, CallDetailRecord CDR) {
InfoHandler infoHandler, CallDetailRecord CDR, OpenviduConfig openviduConfig) {
super(participant.getParticipantPrivateId(), participant.getParticipantPublicId(), participant.getToken(),
participant.getClientMetadata());
this.openviduConfig = openviduConfig;
this.session = kurentoSession;
this.pipeline = pipeline;
this.publisher = new PublisherEndpoint(webParticipant, this, participant.getParticipantPublicId(), pipeline);
this.publisher = new PublisherEndpoint(webParticipant, this, participant.getParticipantPublicId(), pipeline,
this.openviduConfig);
for (Participant other : session.getParticipants()) {
if (!other.getParticipantPublicId().equals(this.getParticipantPublicId())) {
@ -108,6 +108,9 @@ public class KurentoParticipant extends Participant {
this.publisher.getEndpoint().addTag("name", publisherStreamId);
addEndpointListeners(this.publisher);
// Remove streamId from publisher's map
this.session.publishedStreamIds.putIfAbsent(this.getPublisherStreamId(), this.getParticipantPrivateId());
CDR.recordNewPublisher(this, this.session.getSessionId(), mediaOptions);
}
@ -247,7 +250,8 @@ public class KurentoParticipant extends Participant {
log.info("PARTICIPANT {}: unpublishing media stream from room {}", this.getParticipantPublicId(),
this.session.getSessionId());
releasePublisherEndpoint(reason);
this.publisher = new PublisherEndpoint(webParticipant, this, this.getParticipantPublicId(), pipeline);
this.publisher = new PublisherEndpoint(webParticipant, this, this.getParticipantPublicId(), pipeline,
this.openviduConfig);
log.info("PARTICIPANT {}: released publisher endpoint and left it initialized (ready for future streaming)",
this.getParticipantPublicId());
}
@ -300,7 +304,7 @@ public class KurentoParticipant extends Participant {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
}
String subscriberStreamId = this.getParticipantPublicId() + "_" + kSender.getPublisherStremId();
String subscriberStreamId = this.getParticipantPublicId() + "_" + kSender.getPublisherStreamId();
subscriber.getEndpoint().addTag("name", subscriberStreamId);
@ -350,54 +354,12 @@ public class KurentoParticipant extends Participant {
}
}
public void mutePublishedMedia(MutedMediaType muteType) {
if (muteType == null) {
throw new OpenViduException(Code.MEDIA_MUTE_ERROR_CODE, "Mute type cannot be null");
}
this.getPublisher().mute(muteType);
public void mutePublishedMedia(TrackType trackType) {
this.getPublisher().mute(trackType);
}
public void unmutePublishedMedia() {
if (this.getPublisher().getMuteType() == null) {
log.warn("PARTICIPANT {}: Trying to unmute published media. " + "But media is not muted.",
this.getParticipantPublicId());
} else {
this.getPublisher().unmute();
}
}
public void muteSubscribedMedia(Participant sender, MutedMediaType muteType) {
if (muteType == null) {
throw new OpenViduException(Code.MEDIA_MUTE_ERROR_CODE, "Mute type cannot be null");
}
String senderName = sender.getParticipantPublicId();
SubscriberEndpoint subscriberEndpoint = subscribers.get(senderName);
if (subscriberEndpoint == null || subscriberEndpoint.getEndpoint() == null) {
log.warn("PARTICIPANT {}: Trying to mute incoming media from user {}. "
+ "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName);
} else {
log.debug("PARTICIPANT {}: Mute subscriber endpoint linked to user {}", this.getParticipantPublicId(),
senderName);
subscriberEndpoint.mute(muteType);
}
}
public void unmuteSubscribedMedia(Participant sender) {
String senderName = sender.getParticipantPublicId();
SubscriberEndpoint subscriberEndpoint = subscribers.get(senderName);
if (subscriberEndpoint == null || subscriberEndpoint.getEndpoint() == null) {
log.warn("PARTICIPANT {}: Trying to unmute incoming media from user {}. "
+ "But there is no such subscriber endpoint.", this.getParticipantPublicId(), senderName);
} else {
if (subscriberEndpoint.getMuteType() == null) {
log.warn("PARTICIPANT {}: Trying to unmute incoming media from user {}. " + "But media is not muted.",
this.getParticipantPublicId(), senderName);
} else {
log.debug("PARTICIPANT {}: Unmute subscriber endpoint linked to user {}", this.getParticipantPublicId(),
senderName);
subscriberEndpoint.unmute();
}
}
public void unmutePublishedMedia(TrackType trackType) {
this.getPublisher().unmute(trackType);
}
public void close(String reason) {
@ -433,7 +395,8 @@ public class KurentoParticipant extends Participant {
*/
public SubscriberEndpoint getNewOrExistingSubscriber(String senderPublicId) {
SubscriberEndpoint sendingEndpoint = new SubscriberEndpoint(webParticipant, this, senderPublicId, pipeline);
SubscriberEndpoint sendingEndpoint = new SubscriberEndpoint(webParticipant, this, senderPublicId, pipeline,
this.openviduConfig);
SubscriberEndpoint existingSendingEndpoint = this.subscribers.putIfAbsent(senderPublicId, sendingEndpoint);
if (existingSendingEndpoint != null) {
@ -468,6 +431,10 @@ public class KurentoParticipant extends Participant {
private void releasePublisherEndpoint(String reason) {
if (publisher != null && publisher.getEndpoint() != null) {
// Store streamId from publisher's map
this.session.publishedStreamIds.remove(this.getPublisherStreamId());
publisher.unregisterErrorListeners();
for (MediaElement el : publisher.getMediaElements()) {
releaseElement(getParticipantPublicId(), el);
@ -695,7 +662,7 @@ public class KurentoParticipant extends Participant {
}
@Override
public String getPublisherStremId() {
public String getPublisherStreamId() {
return this.publisher.getEndpoint().getTag("name");
}

View File

@ -25,7 +25,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@ -43,10 +42,9 @@ import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.cdr.CallDetailRecord;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
import io.openvidu.server.kurento.endpoint.PublisherEndpoint;
import io.openvidu.server.kurento.endpoint.SubscriberEndpoint;
/**
* @author Pablo Fuente (pablofuenteperez@gmail.com)
@ -56,6 +54,8 @@ public class KurentoSession implements Session {
private final static Logger log = LoggerFactory.getLogger(Session.class);
public static final int ASYNC_LATCH_TIMEOUT = 30;
private OpenviduConfig openviduConfig;
private final ConcurrentMap<String, KurentoParticipant> participants = new ConcurrentHashMap<>();
private String sessionId;
private SessionProperties sessionProperties;
@ -78,14 +78,18 @@ public class KurentoSession implements Session {
private CallDetailRecord CDR;
public final ConcurrentHashMap<String, String> publishedStreamIds = new ConcurrentHashMap<>();
public KurentoSession(String sessionId, SessionProperties sessionProperties, KurentoClient kurentoClient,
KurentoSessionEventsHandler kurentoSessionHandler, boolean destroyKurentoClient, CallDetailRecord CDR) {
KurentoSessionEventsHandler kurentoSessionHandler, boolean destroyKurentoClient, CallDetailRecord CDR,
OpenviduConfig openviduConfig) {
this.sessionId = sessionId;
this.sessionProperties = sessionProperties;
this.kurentoClient = kurentoClient;
this.destroyKurentoClient = destroyKurentoClient;
this.kurentoSessionHandler = kurentoSessionHandler;
this.CDR = CDR;
this.openviduConfig = openviduConfig;
log.debug("New SESSION instance with id '{}'", sessionId);
}
@ -105,7 +109,7 @@ public class KurentoSession implements Session {
createPipeline();
KurentoParticipant kurentoParticipant = new KurentoParticipant(participant, this, getPipeline(),
kurentoSessionHandler.getInfoHandler(), this.CDR);
kurentoSessionHandler.getInfoHandler(), this.CDR, this.openviduConfig);
participants.put(participant.getParticipantPrivateId(), kurentoParticipant);
filterStates.forEach((filterId, state) -> {
@ -199,11 +203,6 @@ public class KurentoSession implements Session {
return null;
}
public Set<SubscriberEndpoint> getAllSubscribersForPublisher(PublisherEndpoint publisher) {
return this.participants.values().stream().flatMap(kp -> kp.getConnectedSubscribedEndpoints(publisher).stream())
.collect(Collectors.toSet());
}
@Override
public boolean close(String reason) {
if (!closed) {
@ -392,4 +391,8 @@ public class KurentoSession implements Session {
return json;
}
public String getParticipantPrivateIdFromStreamId(String streamId) {
return this.publishedStreamIds.get(streamId);
}
}

View File

@ -17,7 +17,9 @@
package io.openvidu.server.kurento.core;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.kurento.client.IceCandidate;
@ -36,20 +38,20 @@ import com.google.gson.JsonSyntaxException;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.MediaMode;
import io.openvidu.java.client.RecordingLayout;
import io.openvidu.java.client.RecordingMode;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.MediaMode;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
import io.openvidu.server.core.SessionManager;
import io.openvidu.server.kurento.KurentoClientProvider;
import io.openvidu.server.kurento.KurentoClientSessionInfo;
import io.openvidu.server.kurento.OpenViduKurentoClientSessionInfo;
import io.openvidu.server.kurento.endpoint.SdpType;
import io.openvidu.server.rpc.RpcHandler;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
public class KurentoSessionManager extends SessionManager {
@ -105,7 +107,8 @@ public class KurentoSessionManager extends SessionManager {
}
@Override
public synchronized void leaveRoom(Participant participant, Integer transactionId, String reason, boolean closeWebSocket) {
public synchronized void leaveRoom(Participant participant, Integer transactionId, String reason,
boolean closeWebSocket) {
log.debug("Request [LEAVE_ROOM] ({})", participant.getParticipantPublicId());
KurentoParticipant kParticipant = (KurentoParticipant) participant;
@ -171,8 +174,8 @@ public class KurentoSessionManager extends SessionManager {
log.info("Last participant left. Stopping recording for session {}", sessionId);
recordingService.stopRecording(session);
evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID)
.getParticipantPrivateId(), "EVICT_RECORDER");
evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null,
null, "EVICT_RECORDER");
}
// Finally close websocket session if required
@ -261,13 +264,13 @@ public class KurentoSessionManager extends SessionManager {
participants = kurentoParticipant.getSession().getParticipants();
if (sdpAnswer != null) {
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStremId(), session.getSessionId(),
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(), session.getSessionId(),
mediaOptions, sdpAnswer, participants, transactionId, null);
}
}
@Override
public void unpublishVideo(Participant participant, Integer transactionId, String reason) {
public void unpublishVideo(Participant participant, Participant moderator, Integer transactionId, String reason) {
try {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
KurentoSession session = kParticipant.getSession();
@ -282,11 +285,12 @@ public class KurentoSessionManager extends SessionManager {
Set<Participant> participants = session.getParticipants();
sessionEventsHandler.onUnpublishMedia(participant, participants, transactionId, null, reason);
sessionEventsHandler.onUnpublishMedia(participant, participants, moderator, transactionId, null, reason);
} catch (OpenViduException e) {
log.warn("PARTICIPANT {}: Error unpublishing media", participant.getParticipantPublicId(), e);
sessionEventsHandler.onUnpublishMedia(participant, null, transactionId, e, "");
sessionEventsHandler.onUnpublishMedia(participant, new HashSet<>(Arrays.asList(participant)), moderator,
transactionId, e, "");
}
}
@ -327,10 +331,10 @@ public class KurentoSessionManager extends SessionManager {
}
} catch (OpenViduException e) {
log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e);
sessionEventsHandler.onSubscribe(participant, session, senderName, null, transactionId, e);
sessionEventsHandler.onSubscribe(participant, session, null, transactionId, e);
}
if (sdpAnswer != null) {
sessionEventsHandler.onSubscribe(participant, session, senderName, sdpAnswer, transactionId, null);
sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null);
}
}
@ -353,7 +357,7 @@ public class KurentoSessionManager extends SessionManager {
kParticipant.cancelReceivingMedia(senderName, "unsubscribe");
sessionEventsHandler.onUnsubscribe(participant, senderName, transactionId, null);
sessionEventsHandler.onUnsubscribe(participant, transactionId, null);
}
@Override
@ -373,7 +377,7 @@ public class KurentoSessionManager extends SessionManager {
public void streamPropertyChanged(Participant participant, Integer transactionId, String streamId, String property,
JsonElement newValue, String reason) {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
streamId = kParticipant.getPublisherStremId();
streamId = kParticipant.getPublisherStreamId();
MediaOptions streamProperties = kParticipant.getPublisherMediaOptions();
Boolean hasAudio = streamProperties.hasAudio();
@ -440,7 +444,7 @@ public class KurentoSessionManager extends SessionManager {
}
KurentoClient kurentoClient = kcProvider.getKurentoClient(kcSessionInfo);
session = new KurentoSession(sessionId, sessionProperties, kurentoClient, kurentoSessionEventsHandler,
kcProvider.destroyWhenUnused(), this.CDR);
kcProvider.destroyWhenUnused(), this.CDR, this.openviduConfig);
KurentoSession oldSession = (KurentoSession) sessions.putIfAbsent(sessionId, session);
if (oldSession != null) {
@ -456,19 +460,25 @@ public class KurentoSessionManager extends SessionManager {
sessionEventsHandler.onSessionCreated(sessionId);
}
/**
* Application-originated request to remove a participant from a session. <br/>
* <strong>Side effects:</strong> The session event handler should notify the
* participant that she has been evicted. Should also send notifications to all
* other participants about the one that's just been evicted.
*
*/
@Override
public void evictParticipant(String participantPrivateId, String reason) throws OpenViduException {
Participant participant = this.getParticipant(participantPrivateId);
this.leaveRoom(participant, null, reason, false);
sessionEventsHandler.onParticipantEvicted(participant, reason);
sessionEventsHandler.closeRpcSession(participantPrivateId);
public void evictParticipant(Participant evictedParticipant, Participant moderator, Integer transactionId,
String reason) throws OpenViduException {
if (evictedParticipant != null) {
KurentoParticipant kParticipant = (KurentoParticipant) evictedParticipant;
Set<Participant> participants = kParticipant.getSession().getParticipants();
this.leaveRoom(kParticipant, null, reason, false);
this.sessionEventsHandler.onForceDisconnect(moderator, evictedParticipant, participants, transactionId,
null, reason);
sessionEventsHandler.closeRpcSession(evictedParticipant.getParticipantPrivateId());
} else {
if (moderator != null && transactionId != null) {
this.sessionEventsHandler.onForceDisconnect(moderator, evictedParticipant,
new HashSet<>(Arrays.asList(moderator)), transactionId,
new OpenViduException(Code.USER_NOT_FOUND_ERROR_CODE,
"Connection not found when calling 'forceDisconnect'"),
"");
}
}
}
@Override
@ -509,4 +519,21 @@ public class KurentoSessionManager extends SessionManager {
typeOfVideo, frameRate, videoDimensions, doLoopback);
}
@Override
public boolean unpublishStream(Session session, String streamId, Participant moderator, Integer transactionId,
String reason) {
String participantPrivateId = ((KurentoSession) session).getParticipantPrivateIdFromStreamId(streamId);
if (participantPrivateId != null) {
Participant participant = this.getParticipant(participantPrivateId);
if (participant != null) {
this.unpublishVideo(participant, moderator, transactionId, reason);
return true;
} else {
return false;
}
} else {
return false;
}
}
}

View File

@ -44,8 +44,8 @@ import org.slf4j.LoggerFactory;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.Participant;
import io.openvidu.server.kurento.MutedMediaType;
import io.openvidu.server.kurento.core.KurentoParticipant;
/**
@ -58,12 +58,18 @@ import io.openvidu.server.kurento.core.KurentoParticipant;
*/
public abstract class MediaEndpoint {
private static Logger log;
private OpenviduConfig openviduConfig;
private boolean web = false;
private WebRtcEndpoint webEndpoint = null;
private RtpEndpoint endpoint = null;
private final int maxRecvKbps;
private final int minRecvKbps;
private final int maxSendKbps;
private final int minSendKbps;
private KurentoParticipant owner;
private String endpointName;
@ -73,8 +79,6 @@ public abstract class MediaEndpoint {
private final List<IceCandidate> receivedCandidateList = new LinkedList<IceCandidate>();
private LinkedList<IceCandidate> candidates = new LinkedList<IceCandidate>();
private MutedMediaType muteType;
public Map<String, MediaType> flowInMedia = new ConcurrentHashMap<>();
public Map<String, MediaType> flowOutMedia = new ConcurrentHashMap<>();
@ -92,7 +96,7 @@ public abstract class MediaEndpoint {
* @param log
*/
public MediaEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline,
Logger log) {
OpenviduConfig openviduConfig, Logger log) {
if (log == null) {
MediaEndpoint.log = LoggerFactory.getLogger(MediaEndpoint.class);
} else {
@ -102,6 +106,12 @@ public abstract class MediaEndpoint {
this.owner = owner;
this.setEndpointName(endpointName);
this.setMediaPipeline(pipeline);
this.openviduConfig = openviduConfig;
this.maxRecvKbps = this.openviduConfig.getVideoMaxRecvBandwidth();
this.minRecvKbps = this.openviduConfig.getVideoMinRecvBandwidth();
this.maxSendKbps = this.openviduConfig.getVideoMaxSendBandwidth();
this.minSendKbps = this.openviduConfig.getVideoMinSendBandwidth();
}
public boolean isWeb() {
@ -204,50 +214,6 @@ public abstract class MediaEndpoint {
unregisterElementErrListener(endpoint, endpointSubscription);
}
/**
* Mute the media stream.
*
* @param muteType
* which type of leg to disconnect (audio, video or both)
*/
public abstract void mute(MutedMediaType muteType);
/**
* Reconnect the muted media leg(s).
*/
public abstract void unmute();
public void setMuteType(MutedMediaType muteType) {
this.muteType = muteType;
}
public MutedMediaType getMuteType() {
return this.muteType;
}
protected void resolveCurrentMuteType(MutedMediaType newMuteType) {
MutedMediaType prev = this.getMuteType();
if (prev != null) {
switch (prev) {
case AUDIO:
if (muteType.equals(MutedMediaType.VIDEO)) {
this.setMuteType(MutedMediaType.ALL);
return;
}
break;
case VIDEO:
if (muteType.equals(MutedMediaType.AUDIO)) {
this.setMuteType(MutedMediaType.ALL);
return;
}
break;
case ALL:
return;
}
}
this.setMuteType(newMuteType);
}
/**
* Creates the endpoint (RTP or WebRTC) and any other additional elements (if
* needed).
@ -265,10 +231,10 @@ public abstract class MediaEndpoint {
public void onSuccess(WebRtcEndpoint result) throws Exception {
webEndpoint = result;
webEndpoint.setMaxVideoRecvBandwidth(600);
webEndpoint.setMinVideoRecvBandwidth(300);
webEndpoint.setMaxVideoSendBandwidth(600);
webEndpoint.setMinVideoSendBandwidth(300);
webEndpoint.setMaxVideoRecvBandwidth(maxRecvKbps);
webEndpoint.setMinVideoRecvBandwidth(minRecvKbps);
webEndpoint.setMaxVideoSendBandwidth(maxSendKbps);
webEndpoint.setMinVideoSendBandwidth(minSendKbps);
endpointLatch.countDown();
log.trace("EP {}: Created a new WebRtcEndpoint", endpointName);

View File

@ -36,8 +36,9 @@ import org.slf4j.LoggerFactory;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.kurento.MutedMediaType;
import io.openvidu.server.kurento.TrackType;
import io.openvidu.server.kurento.core.KurentoParticipant;
/**
@ -46,6 +47,7 @@ import io.openvidu.server.kurento.core.KurentoParticipant;
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class PublisherEndpoint extends MediaEndpoint {
private final static Logger log = LoggerFactory.getLogger(PublisherEndpoint.class);
protected MediaOptions mediaOptions;
@ -59,8 +61,8 @@ public class PublisherEndpoint extends MediaEndpoint {
private Map<String, ListenerSubscription> elementsErrorSubscriptions = new HashMap<String, ListenerSubscription>();
public PublisherEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline) {
super(web, owner, endpointName, pipeline, log);
public PublisherEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline, OpenviduConfig openviduConfig) {
super(web, owner, endpointName, pipeline, openviduConfig, log);
}
@Override
@ -283,8 +285,7 @@ public class PublisherEndpoint extends MediaEndpoint {
}
}
@Override
public synchronized void mute(MutedMediaType muteType) {
public synchronized void mute(TrackType muteType) {
MediaElement sink = passThru;
if (!elements.isEmpty()) {
String sinkId = elementIds.peekLast();
@ -308,11 +309,9 @@ public class PublisherEndpoint extends MediaEndpoint {
internalSinkDisconnect(this.getEndpoint(), sink, MediaType.VIDEO);
break;
}
resolveCurrentMuteType(muteType);
}
@Override
public synchronized void unmute() {
public synchronized void unmute(TrackType muteType) {
MediaElement sink = passThru;
if (!elements.isEmpty()) {
String sinkId = elementIds.peekLast();
@ -325,8 +324,17 @@ public class PublisherEndpoint extends MediaEndpoint {
} else {
log.debug("Will unmute connection of WebRTC and PassThrough (no other elems)");
}
internalSinkConnect(this.getEndpoint(), sink);
setMuteType(null);
switch (muteType) {
case ALL:
internalSinkConnect(this.getEndpoint(), sink);
break;
case AUDIO:
internalSinkConnect(this.getEndpoint(), sink, MediaType.AUDIO);
break;
case VIDEO:
internalSinkConnect(this.getEndpoint(), sink, MediaType.VIDEO);
break;
}
}
private String getNext(String uid) {
@ -466,7 +474,7 @@ public class PublisherEndpoint extends MediaEndpoint {
});
}
}
@Override
public PublisherEndpoint getPublisher() {
return this;

View File

@ -19,12 +19,10 @@ package io.openvidu.server.kurento.endpoint;
import org.json.simple.JSONObject;
import org.kurento.client.MediaPipeline;
import org.kurento.client.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.kurento.core.KurentoParticipant;
/**
@ -39,8 +37,8 @@ public class SubscriberEndpoint extends MediaEndpoint {
private PublisherEndpoint publisher = null;
public SubscriberEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline) {
super(web, owner, endpointName, pipeline, log);
public SubscriberEndpoint(boolean web, KurentoParticipant owner, String endpointName, MediaPipeline pipeline, OpenviduConfig openviduConfig) {
super(web, owner, endpointName, pipeline, openviduConfig, log);
}
public synchronized String subscribe(String sdpOffer, PublisherEndpoint publisher) {
@ -70,31 +68,6 @@ public class SubscriberEndpoint extends MediaEndpoint {
this.publisher = publisher;
}
@Override
public synchronized void mute(io.openvidu.server.kurento.MutedMediaType muteType) {
if (this.publisher == null) {
throw new OpenViduException(Code.MEDIA_MUTE_ERROR_CODE, "Publisher endpoint not found");
}
switch (muteType) {
case ALL:
this.publisher.disconnectFrom(this.getEndpoint());
break;
case AUDIO:
this.publisher.disconnectFrom(this.getEndpoint(), MediaType.AUDIO);
break;
case VIDEO:
this.publisher.disconnectFrom(this.getEndpoint(), MediaType.VIDEO);
break;
}
resolveCurrentMuteType(muteType);
}
@Override
public synchronized void unmute() {
this.publisher.connect(this.getEndpoint());
setMuteType(null);
}
@SuppressWarnings("unchecked")
@Override
public JSONObject toJSON() {

View File

@ -43,6 +43,7 @@ import io.openvidu.java.client.RecordingMode;
import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.ParticipantRole;
import io.openvidu.server.core.Session;
import io.openvidu.server.core.SessionManager;
@ -177,6 +178,46 @@ public class SessionRestController {
}
}
@RequestMapping(value = "/sessions/{sessionId}/connection/{connectionId}", method = RequestMethod.DELETE)
public ResponseEntity<JSONObject> disconnectParticipant(@PathVariable("sessionId") String sessionId,
@PathVariable("connectionId") String participantPublicId) {
Session session = this.sessionManager.getSession(sessionId);
if (session != null) {
Participant participant = session.getParticipantByPublicId(participantPublicId);
if (participant != null) {
this.sessionManager.evictParticipant(participant, null, null, "forceDisconnectByServer");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
@RequestMapping(value = "/sessions/{sessionId}/stream/{streamId}", method = RequestMethod.DELETE)
public ResponseEntity<JSONObject> unpublishStream(@PathVariable("sessionId") String sessionId,
@PathVariable("streamId") String streamId) {
Session session = this.sessionManager.getSession(sessionId);
if (session != null) {
if (this.sessionManager.unpublishStream(session, streamId, null, null, "forceUnpublishByServer")) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
} else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
/*
* @RequestMapping(value = "/sessions/{sessionId}/stream/{streamId}", method =
* RequestMethod.PUT) public ResponseEntity<JSONObject>
* muteMedia(@PathVariable("sessionId") String sessionId,
*
* @PathVariable("streamId") String streamId, @RequestBody Map<?, ?> params) { }
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = "/tokens", method = RequestMethod.POST)
public ResponseEntity<JSONObject> newToken(@RequestBody Map<?, ?> params) {
@ -290,8 +331,9 @@ public class SessionRestController {
Recording stoppedRecording = this.recordingService.stopRecording(session);
sessionManager.evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID)
.getParticipantPrivateId(), "EVICT_RECORDER");
sessionManager.evictParticipant(
session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null, null,
"EVICT_RECORDER");
return new ResponseEntity<>(stoppedRecording.toJson(), HttpStatus.OK);
}

View File

@ -56,7 +56,6 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
RpcNotificationService notificationService;
private ConcurrentMap<String, Boolean> webSocketEOFTransportError = new ConcurrentHashMap<>();
// private ConcurrentMap<String, Boolean> webSocketBrokenPipeTransportError = new ConcurrentHashMap<>();
@Override
public void handleRequest(Transaction transaction, Request<JsonObject> request) throws Exception {
@ -126,6 +125,12 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
case ProtocolElements.STREAMPROPERTYCHANGED_METHOD:
streamPropertyChanged(rpcConnection, request);
break;
case ProtocolElements.FORCEDISCONNECT_METHOD:
forceDisconnect(rpcConnection, request);
break;
case ProtocolElements.FORCEUNPUBLISH_METHOD:
forceUnpublish(rpcConnection, request);
break;
default:
log.error("Unrecognized request {}", request);
break;
@ -190,36 +195,27 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
private void leaveRoom(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
if (sessionId == null) { // null when afterConnectionClosed
log.warn("No session information found for participant with privateId {}. "
+ "Using the admin method to evict the user.", participantPrivateId);
leaveRoomAfterConnClosed(participantPrivateId, "");
} else {
// Sanity check: don't call leaveRoom unless the id checks out
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
if (participant != null) {
log.info("Participant {} is leaving session {}", participant.getParticipantPublicId(), sessionId);
sessionManager.leaveRoom(participant, request.getId(), "disconnect", true);
log.info("Participant {} has left session {}", participant.getParticipantPublicId(), sessionId);
} else {
log.warn("Participant with private id {} not found in session {}. "
+ "Using the admin method to evict the user.", participantPrivateId, sessionId);
leaveRoomAfterConnClosed(participantPrivateId, "");
}
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "disconnect");
} catch (OpenViduException e) {
return;
}
sessionManager.leaveRoom(participant, request.getId(), "disconnect", true);
log.info("Participant {} has left session {}", participant.getParticipantPublicId(),
rpcConnection.getSessionId());
}
private void publishVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "publish");
} catch (OpenViduException e) {
return;
}
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
if (sessionManager.isPublisherInSession(sessionId, participant)) {
if (sessionManager.isPublisherInSession(rpcConnection.getSessionId(), participant)) {
MediaOptions options = sessionManager.generateMediaOptions(request);
sessionManager.publishVideo(participant, options, request.getId());
} else {
@ -230,10 +226,12 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
private void receiveVideoFrom(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "subscribe");
} catch (OpenViduException e) {
return;
}
String senderName = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
senderName = senderName.substring(0, senderName.indexOf("_"));
@ -243,21 +241,24 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
private void unsubscribeFromVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "unsubscribe");
} catch (OpenViduException e) {
return;
}
String senderName = getStringParam(request, ProtocolElements.UNSUBSCRIBEFROMVIDEO_SENDER_PARAM);
sessionManager.unsubscribe(participant, senderName, request.getId());
}
private void onIceCandidate(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "onIceCandidate");
} catch (OpenViduException e) {
return;
}
String endpointName = getStringParam(request, ProtocolElements.ONICECANDIDATE_EPNAME_PARAM);
String candidate = getStringParam(request, ProtocolElements.ONICECANDIDATE_CANDIDATE_PARAM);
@ -268,41 +269,88 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
private void sendMessage(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "signal");
} catch (OpenViduException e) {
return;
}
String message = getStringParam(request, ProtocolElements.SENDMESSAGE_MESSAGE_PARAM);
sessionManager.sendMessage(participant, message, request.getId());
}
private void unpublishVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "unpublish");
} catch (OpenViduException e) {
return;
}
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
sessionManager.unpublishVideo(participant, request.getId(), "unpublish");
sessionManager.unpublishVideo(participant, null, request.getId(), "unpublish");
}
public void streamPropertyChanged(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
private void forceDisconnect(RpcConnection rpcConnection, Request<JsonObject> request) {
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "forceDisconnect");
} catch (OpenViduException e) {
return;
}
if (sessionManager.isModeratorInSession(rpcConnection.getSessionId(), participant)) {
String connectionId = getStringParam(request, ProtocolElements.FORCEDISCONNECT_CONNECTIONID_PARAM);
sessionManager.evictParticipant(
sessionManager.getSession(rpcConnection.getSessionId()).getParticipantByPublicId(connectionId),
participant, request.getId(), "forceDisconnectByUser");
} else {
log.error("Error: participant {} is not a moderator", participant.getParticipantPublicId());
throw new OpenViduException(Code.USER_UNAUTHORIZED_ERROR_CODE,
"Unable to force disconnect. The user does not have a valid token");
}
}
private void forceUnpublish(RpcConnection rpcConnection, Request<JsonObject> request) {
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "forceUnpublish");
} catch (OpenViduException e) {
return;
}
if (sessionManager.isModeratorInSession(rpcConnection.getSessionId(), participant)) {
String streamId = getStringParam(request, ProtocolElements.FORCEUNPUBLISH_STREAMID_PARAM);
sessionManager.unpublishStream(sessionManager.getSession(rpcConnection.getSessionId()), streamId,
participant, request.getId(), "forceUnpublishByUser");
} else {
log.error("Error: participant {} is not a moderator", participant.getParticipantPublicId());
throw new OpenViduException(Code.USER_UNAUTHORIZED_ERROR_CODE,
"Unable to force unpublish. The user does not have a valid token");
}
}
private void streamPropertyChanged(RpcConnection rpcConnection, Request<JsonObject> request) {
Participant participant;
try {
participant = sanityCheckOfSession(rpcConnection, "onStreamPropertyChanged");
} catch (OpenViduException e) {
return;
}
String streamId = getStringParam(request, ProtocolElements.STREAMPROPERTYCHANGED_STREAMID_PARAM);
String property = getStringParam(request, ProtocolElements.STREAMPROPERTYCHANGED_PROPERTY_PARAM);
JsonElement newValue = getParam(request, ProtocolElements.STREAMPROPERTYCHANGED_NEWVALUE_PARAM);
String reason = getStringParam(request, ProtocolElements.STREAMPROPERTYCHANGED_REASON_PARAM);
sessionManager.streamPropertyChanged(participant, request.getId(), streamId, property, newValue, reason);
}
public void leaveRoomAfterConnClosed(String participantPrivateId, String reason) {
try {
sessionManager.evictParticipant(participantPrivateId, reason);
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
reason);
log.info("Evicted participant with privateId {}", participantPrivateId);
} catch (OpenViduException e) {
log.warn("Unable to evict: {}", e.getMessage());
@ -360,7 +408,6 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
if ("IOException".equals(exception.getClass().getSimpleName())
&& "Broken pipe".equals(exception.getCause().getMessage())) {
log.warn("Parcipant with private id {} unexpectedly closed the websocket", rpcSession.getSessionId());
// this.webSocketBrokenPipeTransportError.put(rpcSession.getSessionId(), true);
}
if ("EOFException".equals(exception.getClass().getSimpleName())) {
// Store WebSocket connection interrupted exception for this web socket to
@ -402,7 +449,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
return request.getParams().get(key).getAsBoolean();
}
public static JsonElement getParam(Request<JsonObject> request, String key) {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod()
@ -411,4 +458,33 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
return request.getParams().get(key);
}
private Participant sanityCheckOfSession(RpcConnection rpcConnection, String methodName) throws OpenViduException {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
String errorMsg;
if (sessionId == null) { // null when afterConnectionClosed
errorMsg = "No session information found for participant with privateId " + participantPrivateId
+ ". Using the admin method to evict the user.";
log.warn(errorMsg);
leaveRoomAfterConnClosed(participantPrivateId, "");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, errorMsg);
} else {
// Sanity check: don't call RPC method unless the id checks out
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
if (participant != null) {
errorMsg = "Participant " + participant.getParticipantPublicId() + " is calling method '" + methodName
+ "' in session " + sessionId;
log.info(errorMsg);
return participant;
} else {
errorMsg = "Participant with private id " + participantPrivateId + " not found in session " + sessionId
+ ". Using the admin method to evict the user.";
log.warn(errorMsg);
leaveRoomAfterConnClosed(participantPrivateId, "");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, errorMsg);
}
}
}
}

View File

@ -64,6 +64,54 @@
"type": "java.lang.String",
"description": "Path to COTURN sqlite database to add and remove TURN user credentials",
"defaultValue": "/opt/openvidu/coturn/turndb"
},
{
"name": "openvidu.streams.video.max-recv-bandwidth",
"type": "java.lang.Integer",
"description": "Maximum video bandwith sent from clients to OpenVidu Server, in kbps. 0 means unconstrained",
"defaultValue": 600
},
{
"name": "openvidu.streams.video.min-recv-bandwidth",
"type": "java.lang.Integer",
"description": "Minimum video bandwith sent from clients to OpenVidu Server, in kbps. 0 means unconstrained",
"defaultValue": 300
},
{
"name": "openvidu.streams.video.max-send-bandwidth",
"type": "java.lang.Integer",
"description": "Maximum video bandwith sent from OpenVidu Server to clients, in kbps. 0 means unconstrained",
"defaultValue": 600
},
{
"name": "openvidu.streams.video.min-send-bandwidth",
"type": "java.lang.Integer",
"description": "Minimum video bandwith sent from OpenVidu Server to clients, in kbps. 0 means unconstrained",
"defaultValue": 300
},
{
"name": "coturn.redis.ip",
"type": "java.lang.String",
"description": "Redis IP where OpenVidu Server should connect to store TURN credentials",
"defaultValue": "127.0.0.1"
},
{
"name": "coturn.redis.dbname",
"type": "java.lang.String",
"description": "Redis database where to store TURN credentials",
"defaultValue": "0"
},
{
"name": "coturn.redis.password",
"type": "java.lang.String",
"description": "Password to connect OpenVidu Server to Redis database to store TURN credentials",
"defaultValue": "turn"
},
{
"name": "coturn.redis.connect-timeout",
"type": "java.lang.Integer",
"description": "Timeout in seconds when OpenVidu Server is connecting to Redis database to store TURN credentials",
"defaultValue": 30
}
]
}

View File

@ -9,13 +9,21 @@ kms.uris=[\"ws://localhost:8888/kurento\"]
openvidu.secret: uincBgf9ysUCIo4MNbrfMg5hsX6FYYak
openvidu.publicurl: https://172.18.2.38:8443
openvidu.cdr: false
openvidu.recording: false
openvidu.recording.path: /home/recordings
openvidu.recording.public-access: false
openvidu.recording.notification: publisher_moderator
openvidu.recording.custom-layout:
kms.uris=[\"ws://localhost:8888/kurento\"]
coturn.redis.ip=127.0.0.1
coturn.redis.dbname=0
coturn.redis.password=turn
coturn.redis.connect-timeout=30
openvidu.recording.custom-layout: /opt/openvidu/custom-layout
openvidu.streams.video.max-recv-bandwidth: 600
openvidu.streams.video.min-recv-bandwidth: 300
openvidu.streams.video.max-send-bandwidth: 600
openvidu.streams.video.min-send-bandwidth: 300
kms.uris: [\"ws://localhost:8888/kurento\"]
coturn.redis.ip: 127.0.0.1
coturn.redis.dbname: 0
coturn.redis.password: turn
coturn.redis.connect-timeout: 30

View File

@ -7,7 +7,7 @@ mat-radio-button:first-child {
margin-left: 0;
}
#turn-conf-label {
.label {
display: block;
font-size: 12px;
color: rgba(0, 0, 0, 0.54);
@ -15,9 +15,8 @@ mat-radio-button:first-child {
margin-bottom: 5px;
}
.not-manual {
padding-top: 6px;
padding-bottom: 15px;
#turn-div {
padding-bottom: 1.25em;
}
#manual-turn-div {
@ -25,4 +24,9 @@ mat-radio-button:first-child {
padding: 5px;
border: 1px solid #00000026;
border-radius: 3px;
}
#role-div {
padding-top: 6px;
padding-bottom: 15px;
}

View File

@ -28,8 +28,8 @@
<mat-form-field>
<input matInput placeholder="CustomSessionId" [(ngModel)]="sessionProperties.customSessionId">
</mat-form-field>
<label id="turn-conf-label">Turn configuration</label>
<div [class.not-manual]="turnConf !== 'manual'">
<label class="label">Turn configuration</label>
<div id="turn-div">
<mat-radio-group name="Turn configuration" [(ngModel)]="turnConf">
<mat-radio-button value="auto">Auto</mat-radio-button>
<mat-radio-button value="freeice">Freeice</mat-radio-button>
@ -47,9 +47,20 @@
</mat-form-field>
</div>
</div>
<label class="label">Role</label>
<div id="role-div">
<mat-radio-group name="Role" [(ngModel)]="participantRole">
<mat-radio-button value="SUBSCRIBER">SUB</mat-radio-button>
<mat-radio-button value="PUBLISHER">PUB</mat-radio-button>
<mat-radio-button value="MODERATOR">MOD</mat-radio-button>
</mat-radio-group>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="undefined">CANCEL</button>
<button mat-button [mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf}">SAVE</button>
<button mat-button [mat-dialog-close]="{sessionProperties: sessionProperties, turnConf: turnConf, manualTurnConf: manualTurnConf, participantRole: participantRole}">SAVE</button>
</mat-dialog-actions>
</div>

View File

@ -13,6 +13,7 @@ export class SessionPropertiesDialogComponent {
sessionProperties: SessionProperties;
turnConf: string;
manualTurnConf: RTCIceServer = {};
participantRole: string;
mediaMode = MediaMode;
recordingMode = RecordingMode;
@ -23,6 +24,7 @@ export class SessionPropertiesDialogComponent {
this.sessionProperties = data.sessionProperties;
this.turnConf = data.turnConf;
this.manualTurnConf = data.manualTurnConf;
this.participantRole = data.participantRole;
}
enumToArray(enumerator: any) {

View File

@ -78,7 +78,7 @@
<div fxLayout="column" class="publisher-btns-div">
<button mat-icon-button title="Publisher properties" [id]="'publisher-settings-btn-' + index" class="mat-icon-custom" (click)="openPublisherPropertiesDialog()"
[disabled]="!publishTo">
<mat-icon class="mat-icon-custom-ic" aria-label="Session properties button">settings</mat-icon>
<mat-icon class="mat-icon-custom-ic" aria-label="Publisher properties button">settings</mat-icon>
</button>
<button mat-icon-button title="Add new publisher to running session" [id]="'session-api-btn-' + index" class="mat-icon-custom"
[disabled]="!session || !publishTo">

View File

@ -6,14 +6,15 @@ import {
import {
OpenVidu, Session, Subscriber, Publisher, VideoInsertMode, StreamEvent, ConnectionEvent,
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, OpenViduError
} from 'openvidu-browser';
import {
OpenVidu as OpenViduAPI,
SessionProperties as SessionPropertiesAPI,
MediaMode,
RecordingMode,
RecordingLayout
RecordingLayout,
OpenViduRole
} from 'openvidu-node-client';
import { MatDialog, MAT_CHECKBOX_CLICK_ACTION } from '@angular/material';
import { ExtensionDialogComponent } from '../dialogs/extension-dialog/extension-dialog.component';
@ -111,6 +112,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
turnConf = 'auto';
manualTurnConf: RTCIceServer = { urls: [] };
participantRole: OpenViduRole = OpenViduRole.PUBLISHER;
events: OpenViduEvent[] = [];
@ -430,7 +432,10 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
this.publisher.subscribeToRemote();
}
this.session.publish(this.publisher);
this.session.publish(this.publisher).catch((error: OpenViduError) => {
console.error(error);
this.session.unpublish(this.publisher);
});
}
syncSubscribe(session: Session, event) {
@ -483,7 +488,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
data: {
sessionProperties: this.sessionProperties,
turnConf: this.turnConf,
manualTurnConf: this.manualTurnConf
manualTurnConf: this.manualTurnConf,
participantRole: this.participantRole
},
width: '280px'
});
@ -496,6 +502,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
this.turnConf = result.turnConf;
this.manualTurnConf = result.manualTurnConf;
this.participantRole = result.participantRole;
}
document.getElementById('session-settings-btn-' + this.index).classList.remove('cdk-program-focused');
});
@ -588,7 +595,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
return OV_NodeClient.createSession(this.sessionProperties)
.then(session_NodeClient => {
return session_NodeClient.generateToken();
return session_NodeClient.generateToken({ role: this.participantRole });
});
}

View File

@ -10,16 +10,16 @@
<button class="video-btn pub-btn" title="Publish/Unpublish" (click)="pubUnpub()">
<mat-icon aria-label="Publish or unpublish" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubIcon}}</mat-icon>
</button>
<button *ngIf="streamManager.stream.hasVideo" class="video-btn pub-video-btn" title="Publish/Unpublish Video" (click)="pubUnpubVideo()">
<button *ngIf="streamManager.stream.hasVideo && !this.unpublished" class="video-btn pub-video-btn" title="Publish/Unpublish Video" (click)="pubUnpubVideo()">
<mat-icon aria-label="Publish or unpublish video" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubVideoIcon}}</mat-icon>
</button>
<button *ngIf="streamManager.stream.hasAudio" class="video-btn pub-audio-btn" title="Publish/Unpublish Audio" (click)="pubUnpubAudio()">
<button *ngIf="streamManager.stream.hasAudio && !this.unpublished" class="video-btn pub-audio-btn" title="Publish/Unpublish Audio" (click)="pubUnpubAudio()">
<mat-icon aria-label="Publish or unpublish audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon>
</button>
<button class="video-btn change-publisher-btn" title="Change publisher" (click)="changePub()">
<button *ngIf="!this.unpublished" class="video-btn change-publisher-btn" title="Change publisher" (click)="changePub()">
<mat-icon aria-label="Change publisher" class="mat-icon material-icons" role="img" aria-hidden="true">switch_video</mat-icon>
</button>
<button class="video-btn rec-btn publisher-rec-btn" title="Record" (click)="record()">
<button *ngIf="!this.unpublished" class="video-btn rec-btn publisher-rec-btn" title="Record" (click)="record()">
<mat-icon aria-label="Start/Stop local recording" class="mat-icon material-icons" role="img" aria-hidden="true">
{{recordIcon}}</mat-icon>
</button>
@ -47,6 +47,14 @@
<button *ngIf="streamManager.stream.hasAudio && !!pubSubAudioIcon" class="video-btn sub-audio-btn" title="Subscribe/Unsubscribe Audio" (click)="subUnsubAudio()">
<mat-icon aria-label="Subscribe or unsubscribe audio" class="mat-icon material-icons" role="img" aria-hidden="true">{{pubSubAudioIcon}}</mat-icon>
</button>
<button *ngIf="OV.session.capabilities.forceUnpublish" class="video-btn force-unpub-btn" title="Force Unpublish" (click)="forceUnpublish()">
<mat-icon aria-label="Force unpublish" class="mat-icon material-icons" role="img" aria-hidden="true">voice_over_off</mat-icon>
</button>
<button *ngIf="OV.session.capabilities.forceDisconnect" class="video-btn force-disconnect-btn" title="Force Disconnect" (click)="forceDisconnect()">
<mat-icon aria-label="Force disconnect" class="mat-icon material-icons" role="img" aria-hidden="true">clear</mat-icon>
</button>
<button *ngIf="!!recordIcon" class="video-btn rec-btn" title="Record" (click)="record()">
<mat-icon aria-label="Start/Stop recording" class="mat-icon material-icons" role="img" aria-hidden="true">{{recordIcon}}</mat-icon>
</button>

View File

@ -450,6 +450,10 @@ export class VideoComponent implements OnInit, OnDestroy {
event: 'streamDestroyed',
content: e.stream.streamId
});
if (e.reason.indexOf('forceUnpublish') !== -1) {
this.unpublished = !this.unpublished;
this.unpublished ? this.pubSubIcon = 'play_arrow' : this.pubSubIcon = 'stop';
}
});
}
} else {
@ -609,4 +613,12 @@ export class VideoComponent implements OnInit, OnDestroy {
}
}
forceUnpublish() {
this.OV.session.forceUnpublish(this.streamManager.stream);
}
forceDisconnect() {
this.OV.session.forceDisconnect(this.streamManager.stream.connection);
}
}