mirror of https://github.com/OpenVidu/openvidu.git
Merge branch 'master' of https://github.com/OpenVidu/openvidu
commit
3e777a9fbc
|
@ -202,6 +202,10 @@ export class LocalRecorder {
|
||||||
this.videoPreview.id = this.id;
|
this.videoPreview.id = this.id;
|
||||||
this.videoPreview.autoplay = true;
|
this.videoPreview.autoplay = true;
|
||||||
|
|
||||||
|
if (platform.name === 'Safari' && platform.product === 'iPhone') {
|
||||||
|
this.videoPreview.setAttribute('playsinline', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof parentElement === 'string') {
|
if (typeof parentElement === 'string') {
|
||||||
this.htmlParentElementId = parentElement;
|
this.htmlParentElementId = parentElement;
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class OpenVidu {
|
||||||
constructor() {
|
constructor() {
|
||||||
console.info("'OpenVidu' initialized");
|
console.info("'OpenVidu' initialized");
|
||||||
|
|
||||||
if (platform.name!!.toLowerCase().indexOf('mobile') !== -1) {
|
if (platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') {
|
||||||
// Listen to orientationchange only on mobile browsers
|
// Listen to orientationchange only on mobile browsers
|
||||||
(<any>window).onorientationchange = () => {
|
(<any>window).onorientationchange = () => {
|
||||||
this.publishers.forEach(publisher => {
|
this.publishers.forEach(publisher => {
|
||||||
|
@ -276,12 +276,23 @@ export class OpenVidu {
|
||||||
*/
|
*/
|
||||||
checkSystemRequirements(): number {
|
checkSystemRequirements(): number {
|
||||||
const browser = platform.name;
|
const browser = platform.name;
|
||||||
const version = platform.version;
|
const family = platform.os!!.family;
|
||||||
|
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
||||||
|
|
||||||
if ((browser !== 'Chrome') && (browser !== 'Chrome Mobile') &&
|
// Reject iPhones and iPads if not Safari ('Safari' also covers Ionic for iOS)
|
||||||
(browser !== 'Firefox') && (browser !== 'Firefox Mobile') && (browser !== 'Firefox for iOS') &&
|
if (family === 'iOS' && (browser !== 'Safari' || userAgent.indexOf('CriOS') !== -1 || userAgent.indexOf('FxiOS') !== -1)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept: Chrome (desktop and Android), Firefox (desktop and Android), Opera (desktop and Android),
|
||||||
|
// Safari (OSX and iOS), Ionic (Android and iOS)
|
||||||
|
if (
|
||||||
|
(browser !== 'Safari') &&
|
||||||
|
(browser !== 'Chrome') && (browser !== 'Chrome Mobile') &&
|
||||||
|
(browser !== 'Firefox') && (browser !== 'Firefox Mobile') &&
|
||||||
(browser !== 'Opera') && (browser !== 'Opera Mobile') &&
|
(browser !== 'Opera') && (browser !== 'Opera Mobile') &&
|
||||||
(browser !== 'Safari') && (browser !== 'Android Browser')) {
|
(browser !== 'Android Browser')
|
||||||
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -290,11 +301,18 @@ export class OpenVidu {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the browser supports screen-sharing. Chrome, Firefox and Opera support screen-sharing
|
* Checks if the browser supports screen-sharing. Desktop Chrome, Firefox and Opera support screen-sharing
|
||||||
* @returns 1 if the browser supports screen-sharing, 0 otherwise
|
* @returns 1 if the browser supports screen-sharing, 0 otherwise
|
||||||
*/
|
*/
|
||||||
checkScreenSharingCapabilities(): number {
|
checkScreenSharingCapabilities(): number {
|
||||||
const browser = platform.name;
|
const browser = platform.name;
|
||||||
|
const family = platform.os!!.family;
|
||||||
|
|
||||||
|
// Reject mobile devices
|
||||||
|
if (family === 'iOS' || family === 'Android' || family === 'Windows Phone') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if ((browser !== 'Chrome') && (browser !== 'Firefox') && (browser !== 'Opera')) {
|
if ((browser !== 'Chrome') && (browser !== 'Firefox') && (browser !== 'Opera')) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -484,7 +502,7 @@ export class OpenVidu {
|
||||||
(platform.name!.indexOf('Firefox') !== -1 && publisherProperties.videoSource === 'window')) {
|
(platform.name!.indexOf('Firefox') !== -1 && publisherProperties.videoSource === 'window')) {
|
||||||
|
|
||||||
if (platform.name !== 'Chrome' && platform.name!.indexOf('Firefox') === -1 && platform.name !== 'Opera') {
|
if (platform.name !== 'Chrome' && platform.name!.indexOf('Firefox') === -1 && platform.name !== 'Opera') {
|
||||||
const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome and Firefox. Detected browser: ' + platform.name);
|
const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox or Opera. Detected browser: ' + platform.name);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -292,6 +292,11 @@ export class Publisher extends StreamManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.videoReference = document.createElement('video');
|
this.videoReference = document.createElement('video');
|
||||||
|
|
||||||
|
if (platform.name === 'Safari' && platform.product === 'iPhone') {
|
||||||
|
this.videoReference.setAttribute('playsinline', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
this.videoReference.srcObject = mediaStream;
|
this.videoReference.srcObject = mediaStream;
|
||||||
|
|
||||||
this.stream.setMediaStream(mediaStream);
|
this.stream.setMediaStream(mediaStream);
|
||||||
|
@ -337,8 +342,8 @@ export class Publisher extends StreamManager {
|
||||||
};
|
};
|
||||||
this.screenShareResizeInterval = setInterval(() => {
|
this.screenShareResizeInterval = setInterval(() => {
|
||||||
const firefoxSettings = mediaStream.getVideoTracks()[0].getSettings();
|
const firefoxSettings = mediaStream.getVideoTracks()[0].getSettings();
|
||||||
const newWidth = (platform.name === 'Chrome') ? this.videoReference.videoWidth : firefoxSettings.width;
|
const newWidth = (platform.name === 'Chrome' || platform.name === 'Opera') ? this.videoReference.videoWidth : firefoxSettings.width;
|
||||||
const newHeight = (platform.name === 'Chrome') ? this.videoReference.videoHeight : firefoxSettings.height;
|
const newHeight = (platform.name === 'Chrome' || platform.name === 'Opera') ? this.videoReference.videoHeight : firefoxSettings.height;
|
||||||
if (this.stream.isLocalStreamPublished &&
|
if (this.stream.isLocalStreamPublished &&
|
||||||
(newWidth !== this.stream.videoDimensions.width ||
|
(newWidth !== this.stream.videoDimensions.width ||
|
||||||
newHeight !== this.stream.videoDimensions.height)) {
|
newHeight !== this.stream.videoDimensions.height)) {
|
||||||
|
|
|
@ -152,7 +152,7 @@ export class Session implements EventDispatcher {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new OpenViduError(OpenViduErrorName.BROWSER_NOT_SUPPORTED, 'Browser ' + platform.name + ' ' + platform.version + ' is not supported in OpenVidu'));
|
reject(new OpenViduError(OpenViduErrorName.BROWSER_NOT_SUPPORTED, 'Browser ' + platform.name + ' for ' + platform.os!!.family + ' is not supported in OpenVidu'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,9 @@ export class StreamManager implements EventDispatcher {
|
||||||
video: document.createElement('video'),
|
video: document.createElement('video'),
|
||||||
id: ''
|
id: ''
|
||||||
};
|
};
|
||||||
|
if (platform.name === 'Safari' && platform.product === 'iPhone') {
|
||||||
|
this.firstVideoElement.video.setAttribute('playsinline', 'true');
|
||||||
|
}
|
||||||
this.targetElement = targEl;
|
this.targetElement = targEl;
|
||||||
this.element = targEl;
|
this.element = targEl;
|
||||||
}
|
}
|
||||||
|
@ -329,6 +332,11 @@ export class StreamManager implements EventDispatcher {
|
||||||
}
|
}
|
||||||
video.autoplay = true;
|
video.autoplay = true;
|
||||||
video.controls = false;
|
video.controls = false;
|
||||||
|
|
||||||
|
if (platform.name === 'Safari' && platform.product === 'iPhone') {
|
||||||
|
video.setAttribute('playsinline', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
if (!video.id) {
|
if (!video.id) {
|
||||||
video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
|
video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
|
||||||
// DEPRECATED property: assign once the property id if the user provided a valid targetElement
|
// DEPRECATED property: assign once the property id if the user provided a valid targetElement
|
||||||
|
|
|
@ -29,7 +29,7 @@ export interface WebRtcPeerConfiguration {
|
||||||
onicecandidate: (event) => void;
|
onicecandidate: (event) => void;
|
||||||
iceServers: RTCIceServer[] | undefined;
|
iceServers: RTCIceServer[] | undefined;
|
||||||
mediaStream?: MediaStream;
|
mediaStream?: MediaStream;
|
||||||
mode?: string; // sendonly, reconly, sendrecv
|
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,18 +156,52 @@ export class WebRtcPeer {
|
||||||
|
|
||||||
console.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
|
console.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
|
||||||
|
|
||||||
this.pc.createOffer(constraints).then(offer => {
|
if (platform.name === 'Safari') {
|
||||||
console.debug('Created SDP offer');
|
// Safari, at least on iOS just seems to support unified plan, whereas in other browsers is not yet ready and considered experimental
|
||||||
return this.pc.setLocalDescription(offer);
|
if (offerAudio) {
|
||||||
}).then(() => {
|
this.pc.addTransceiver('audio', {
|
||||||
const localDescription = this.pc.localDescription;
|
direction: this.configuration.mode,
|
||||||
if (!!localDescription) {
|
});
|
||||||
console.debug('Local description set', localDescription.sdp);
|
|
||||||
resolve(<string>localDescription.sdp);
|
|
||||||
} else {
|
|
||||||
reject('Local description is not defined');
|
|
||||||
}
|
}
|
||||||
}).catch(error => reject(error));
|
|
||||||
|
if (offerVideo) {
|
||||||
|
this.pc.addTransceiver('video', {
|
||||||
|
direction: this.configuration.mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pc
|
||||||
|
.createOffer()
|
||||||
|
.then(offer => {
|
||||||
|
console.debug('Created SDP offer');
|
||||||
|
return this.pc.setLocalDescription(offer);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const localDescription = this.pc.localDescription;
|
||||||
|
|
||||||
|
if (!!localDescription) {
|
||||||
|
console.debug('Local description set', localDescription.sdp);
|
||||||
|
resolve(localDescription.sdp);
|
||||||
|
} else {
|
||||||
|
reject('Local description is not defined');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
} else {
|
||||||
|
this.pc.createOffer(constraints).then(offer => {
|
||||||
|
console.debug('Created SDP offer');
|
||||||
|
return this.pc.setLocalDescription(offer);
|
||||||
|
}).then(() => {
|
||||||
|
const localDescription = this.pc.localDescription;
|
||||||
|
if (!!localDescription) {
|
||||||
|
console.debug('Local description set', localDescription.sdp);
|
||||||
|
resolve(localDescription.sdp);
|
||||||
|
} else {
|
||||||
|
reject('Local description is not defined');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -451,7 +451,7 @@ public abstract class SessionManager {
|
||||||
this.closeSessionAndEmptyCollections(session, reason);
|
this.closeSessionAndEmptyCollections(session, reason);
|
||||||
|
|
||||||
if (recordingService.sessionIsBeingRecorded(session.getSessionId())) {
|
if (recordingService.sessionIsBeingRecorded(session.getSessionId())) {
|
||||||
recordingService.stopRecording(session, reason);
|
recordingService.stopRecording(session, null, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return participants;
|
||||||
|
|
|
@ -175,14 +175,21 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
showTokens();
|
showTokens();
|
||||||
} else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled()
|
} else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled()
|
||||||
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
|
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
|
||||||
&& RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())
|
|
||||||
&& ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
|
&& ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
|
||||||
.equals(remainingParticipants.iterator().next().getParticipantPublicId())) {
|
.equals(remainingParticipants.iterator().next().getParticipantPublicId())) {
|
||||||
|
if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())) {
|
||||||
log.info("Last participant left. Stopping recording for session {}", sessionId);
|
// Immediately stop recording when last real participant left if
|
||||||
recordingService.stopRecording(session, reason);
|
// RecordingMode.ALWAYS
|
||||||
evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null,
|
log.info("Last participant left. Stopping recording for session {}", sessionId);
|
||||||
null, "EVICT_RECORDER");
|
recordingService.stopRecording(session, null, reason);
|
||||||
|
evictParticipant(session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null,
|
||||||
|
null, "EVICT_RECORDER");
|
||||||
|
} else if (RecordingMode.MANUAL.equals(session.getSessionProperties().recordingMode())) {
|
||||||
|
// Start countdown to stop recording if RecordingMode.MANUAL (will be aborted if
|
||||||
|
// a Publisher starts before timeout)
|
||||||
|
log.info("Last participant left. Starting countdown for stopping recording of session {}", sessionId);
|
||||||
|
recordingService.initAutomaticRecordingStopThread(session.getSessionId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally close websocket session if required
|
// Finally close websocket session if required
|
||||||
|
@ -206,14 +213,10 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
* the peer's request by sending it the SDP response (answer or updated offer)
|
* the peer's request by sending it the SDP response (answer or updated offer)
|
||||||
* generated by the WebRTC endpoint on the server.
|
* generated by the WebRTC endpoint on the server.
|
||||||
*
|
*
|
||||||
* @param participant
|
* @param participant Participant publishing video
|
||||||
* Participant publishing video
|
* @param MediaOptions configuration of the stream to publish
|
||||||
* @param MediaOptions
|
* @param transactionId identifier of the Transaction
|
||||||
* configuration of the stream to publish
|
* @throws OpenViduException on error
|
||||||
* @param transactionId
|
|
||||||
* identifier of the Transaction
|
|
||||||
* @throws OpenViduException
|
|
||||||
* on error
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void publishVideo(Participant participant, MediaOptions mediaOptions, Integer transactionId)
|
public void publishVideo(Participant participant, MediaOptions mediaOptions, Integer transactionId)
|
||||||
|
@ -272,16 +275,29 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
|
|
||||||
if (this.openviduConfig.isRecordingModuleEnabled()
|
if (this.openviduConfig.isRecordingModuleEnabled()
|
||||||
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
|
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
|
||||||
&& RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())
|
|
||||||
&& !recordingService.sessionIsBeingRecorded(session.getSessionId())
|
|
||||||
&& session.getActivePublishers() == 0) {
|
&& session.getActivePublishers() == 0) {
|
||||||
// Insecure session recording
|
if (RecordingMode.ALWAYS.equals(session.getSessionProperties().recordingMode())
|
||||||
new Thread(() -> {
|
&& !recordingService.sessionIsBeingRecorded(session.getSessionId())) {
|
||||||
recordingService.startRecording(session,
|
// Insecure session recording
|
||||||
new RecordingProperties.Builder().name("")
|
new Thread(() -> {
|
||||||
.recordingLayout(session.getSessionProperties().defaultRecordingLayout())
|
recordingService.startRecording(session,
|
||||||
.customLayout(session.getSessionProperties().defaultCustomLayout()).build());
|
new RecordingProperties.Builder().name("")
|
||||||
}).start();
|
.recordingLayout(session.getSessionProperties().defaultRecordingLayout())
|
||||||
|
.customLayout(session.getSessionProperties().defaultCustomLayout()).build());
|
||||||
|
}).start();
|
||||||
|
} else if (RecordingMode.MANUAL.equals(session.getSessionProperties().recordingMode())
|
||||||
|
&& recordingService.sessionIsBeingRecorded(session.getSessionId())) {
|
||||||
|
// Abort automatic recording stop (user published before timeout)
|
||||||
|
log.info("Participant {} published before timeout finished. Aborting automatic recording stop",
|
||||||
|
participant.getParticipantPublicId());
|
||||||
|
boolean stopAborted = recordingService.abortAutomaticRecordingStopThread(session.getSessionId());
|
||||||
|
if (stopAborted) {
|
||||||
|
log.info("Automatic recording stopped succesfully aborted");
|
||||||
|
} else {
|
||||||
|
log.info("Automatic recording stopped couldn't be aborted. Recording of session {} has stopped",
|
||||||
|
session.getSessionId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session.newPublisher(participant);
|
session.newPublisher(participant);
|
||||||
|
@ -458,12 +474,10 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
* Creates a session if it doesn't already exist. The session's id will be
|
* Creates a session if it doesn't already exist. The session's id will be
|
||||||
* indicated by the session info bean.
|
* indicated by the session info bean.
|
||||||
*
|
*
|
||||||
* @param kcSessionInfo
|
* @param kcSessionInfo bean that will be passed to the
|
||||||
* bean that will be passed to the {@link KurentoClientProvider} in
|
* {@link KurentoClientProvider} in order to obtain the
|
||||||
* order to obtain the {@link KurentoClient} that will be used by the
|
* {@link KurentoClient} that will be used by the room
|
||||||
* room
|
* @throws OpenViduException in case of error while creating the session
|
||||||
* @throws OpenViduException
|
|
||||||
* in case of error while creating the session
|
|
||||||
*/
|
*/
|
||||||
public void createSession(KurentoClientSessionInfo kcSessionInfo, SessionProperties sessionProperties)
|
public void createSession(KurentoClientSessionInfo kcSessionInfo, SessionProperties sessionProperties)
|
||||||
throws OpenViduException {
|
throws OpenViduException {
|
||||||
|
|
|
@ -31,6 +31,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -87,6 +89,10 @@ public class ComposedRecordingService {
|
||||||
private Map<String, Recording> startingRecordings = new ConcurrentHashMap<>();
|
private Map<String, Recording> startingRecordings = new ConcurrentHashMap<>();
|
||||||
private Map<String, Recording> startedRecordings = new ConcurrentHashMap<>();
|
private Map<String, Recording> startedRecordings = new ConcurrentHashMap<>();
|
||||||
private Map<String, Recording> sessionsRecordings = new ConcurrentHashMap<>();
|
private Map<String, Recording> sessionsRecordings = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, ScheduledFuture<?>> automaticRecordingStopThreads = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private ScheduledThreadPoolExecutor automaticRecordingStopExecutor = new ScheduledThreadPoolExecutor(
|
||||||
|
Runtime.getRuntime().availableProcessors());
|
||||||
|
|
||||||
private final String IMAGE_NAME = "openvidu/openvidu-recording";
|
private final String IMAGE_NAME = "openvidu/openvidu-recording";
|
||||||
private String IMAGE_TAG;
|
private String IMAGE_TAG;
|
||||||
|
@ -155,10 +161,24 @@ public class ComposedRecordingService {
|
||||||
return recording;
|
return recording;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Recording stopRecording(Session session, String reason) {
|
public Recording stopRecording(Session session, String recordingId, String reason) {
|
||||||
Recording recording = this.sessionsRecordings.remove(session.getSessionId());
|
Recording recording;
|
||||||
String containerId = this.sessionsContainers.remove(session.getSessionId());
|
String containerId;
|
||||||
this.startedRecordings.remove(recording.getId());
|
|
||||||
|
if (session == null) {
|
||||||
|
log.warn(
|
||||||
|
"Existing recording {} does not have an active session associated. This usually means the recording"
|
||||||
|
+ " layout did not join a recorded participant or the recording has been automatically"
|
||||||
|
+ " stopped after last user left and timeout passed",
|
||||||
|
recordingId);
|
||||||
|
recording = this.startedRecordings.remove(recordingId);
|
||||||
|
containerId = this.sessionsContainers.remove(recording.getSessionId());
|
||||||
|
this.sessionsRecordings.remove(recording.getSessionId());
|
||||||
|
} else {
|
||||||
|
recording = this.sessionsRecordings.remove(session.getSessionId());
|
||||||
|
containerId = this.sessionsContainers.remove(session.getSessionId());
|
||||||
|
this.startedRecordings.remove(recording.getId());
|
||||||
|
}
|
||||||
|
|
||||||
if (containerId == null) {
|
if (containerId == null) {
|
||||||
|
|
||||||
|
@ -250,7 +270,9 @@ public class ComposedRecordingService {
|
||||||
throw new OpenViduException(Code.RECORDING_REPORT_ERROR_CODE,
|
throw new OpenViduException(Code.RECORDING_REPORT_ERROR_CODE,
|
||||||
"There was an error generating the metadata report file for the recording");
|
"There was an error generating the metadata report file for the recording");
|
||||||
}
|
}
|
||||||
this.sessionHandler.sendRecordingStoppedNotification(session, recording, reason);
|
if (session != null) {
|
||||||
|
this.sessionHandler.sendRecordingStoppedNotification(session, recording, reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return recording;
|
return recording;
|
||||||
}
|
}
|
||||||
|
@ -465,8 +487,7 @@ public class ComposedRecordingService {
|
||||||
private boolean isFileFromRecording(File file, String recordingId, String recordingName) {
|
private boolean isFileFromRecording(File file, String recordingId, String recordingName) {
|
||||||
return (((recordingId + ".info").equals(file.getName()))
|
return (((recordingId + ".info").equals(file.getName()))
|
||||||
|| ((RECORDING_ENTITY_FILE + recordingId).equals(file.getName()))
|
|| ((RECORDING_ENTITY_FILE + recordingId).equals(file.getName()))
|
||||||
|| (recordingName + ".mp4").equals(file.getName())
|
|| (recordingName + ".mp4").equals(file.getName()) || (recordingId + ".jpg").equals(file.getName()));
|
||||||
|| (recordingId + ".jpg").equals(file.getName()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFreeRecordingId(String sessionId, String shortSessionId) {
|
private String getFreeRecordingId(String sessionId, String shortSessionId) {
|
||||||
|
@ -528,4 +549,19 @@ public class ComposedRecordingService {
|
||||||
this.IMAGE_TAG = version;
|
this.IMAGE_TAG = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initAutomaticRecordingStopThread(String sessionId) {
|
||||||
|
final String recordingId = this.sessionsRecordings.get(sessionId).getId();
|
||||||
|
ScheduledFuture<?> future = this.automaticRecordingStopExecutor.schedule(() -> {
|
||||||
|
log.info("Stopping recording {} after 2 minutes wait (no publisher published before timeout)", recordingId);
|
||||||
|
this.stopRecording(null, recordingId, "lastParticipantLeft");
|
||||||
|
this.automaticRecordingStopThreads.remove(sessionId);
|
||||||
|
}, 2, TimeUnit.MINUTES);
|
||||||
|
this.automaticRecordingStopThreads.putIfAbsent(sessionId, future);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean abortAutomaticRecordingStopThread(String sessionId) {
|
||||||
|
ScheduledFuture<?> future = this.automaticRecordingStopThreads.remove(sessionId);
|
||||||
|
return future.cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,7 +336,9 @@ public class SessionRestController {
|
||||||
recordingLayout = RecordingLayout.valueOf(recordingLayoutString);
|
recordingLayout = RecordingLayout.valueOf(recordingLayoutString);
|
||||||
}
|
}
|
||||||
|
|
||||||
customLayout = (customLayout == null) ? session.getSessionProperties().defaultCustomLayout() : customLayout;
|
customLayout = (customLayout == null || customLayout.isEmpty())
|
||||||
|
? session.getSessionProperties().defaultCustomLayout()
|
||||||
|
: customLayout;
|
||||||
|
|
||||||
Recording startedRecording = this.recordingService.startRecording(session, new RecordingProperties.Builder()
|
Recording startedRecording = this.recordingService.startRecording(session, new RecordingProperties.Builder()
|
||||||
.name(name).recordingLayout(recordingLayout).customLayout(customLayout).build());
|
.name(name).recordingLayout(recordingLayout).customLayout(customLayout).build());
|
||||||
|
@ -368,11 +370,14 @@ public class SessionRestController {
|
||||||
|
|
||||||
Session session = sessionManager.getSession(recording.getSessionId());
|
Session session = sessionManager.getSession(recording.getSessionId());
|
||||||
|
|
||||||
Recording stoppedRecording = this.recordingService.stopRecording(session, "recordingStoppedByServer");
|
Recording stoppedRecording = this.recordingService.stopRecording(session, recording.getId(),
|
||||||
|
"recordingStoppedByServer");
|
||||||
|
|
||||||
sessionManager.evictParticipant(
|
if (session != null) {
|
||||||
session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null, null,
|
sessionManager.evictParticipant(
|
||||||
"EVICT_RECORDER");
|
session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null, null,
|
||||||
|
"EVICT_RECORDER");
|
||||||
|
}
|
||||||
|
|
||||||
return new ResponseEntity<>(stoppedRecording.toJson().toString(), getResponseHeaders(), HttpStatus.OK);
|
return new ResponseEntity<>(stoppedRecording.toJson().toString(), getResponseHeaders(), HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { OpenviduParamsService } from './services/openvidu-params.service';
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
|
||||||
openviduURL = 'https://localhost:4443/';
|
openviduURL = 'https://' + window.location.hostname + ':4443/';
|
||||||
openviduSecret = 'MY_SECRET';
|
openviduSecret = 'MY_SECRET';
|
||||||
|
|
||||||
constructor(private router: Router, private openviduParamsService: OpenviduParamsService) { }
|
constructor(private router: Router, private openviduParamsService: OpenviduParamsService) { }
|
||||||
|
|
|
@ -229,6 +229,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('There was an error connecting to the session:', error.code, error.message);
|
console.log('There was an error connecting to the session:', error.code, error.message);
|
||||||
|
alert('Error connecting to the session: ' + error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,10 @@ export interface OpenviduParams {
|
||||||
export class OpenviduParamsService {
|
export class OpenviduParamsService {
|
||||||
|
|
||||||
params: OpenviduParams =
|
params: OpenviduParams =
|
||||||
{
|
{
|
||||||
openviduUrl: 'https://localhost:4443/',
|
openviduUrl: 'https://' + window.location.hostname + ':4443/',
|
||||||
openviduSecret: 'MY_SECRET'
|
openviduSecret: 'MY_SECRET'
|
||||||
};
|
};
|
||||||
|
|
||||||
newParams$ = new Subject<OpenviduParams>();
|
newParams$ = new Subject<OpenviduParams>();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue