Change concept RTMP to Broadcast. Prepare openvidu-browser for broadcast events

pull/789/head
pabloFuente 2023-02-03 13:55:17 +01:00
parent 99e2b1fe97
commit cae40f5493
17 changed files with 85 additions and 47 deletions

View File

@ -866,6 +866,8 @@ export class OpenVidu {
participantEvicted: this.session.onParticipantEvicted.bind(this.session), participantEvicted: this.session.onParticipantEvicted.bind(this.session),
recordingStarted: this.session.onRecordingStarted.bind(this.session), recordingStarted: this.session.onRecordingStarted.bind(this.session),
recordingStopped: this.session.onRecordingStopped.bind(this.session), recordingStopped: this.session.onRecordingStopped.bind(this.session),
broadcastStarted: this.session.onBroadcastStarted.bind(this.session),
broadcastStopped: this.session.onBroadcastStopped.bind(this.session),
sendMessage: this.session.onNewMessage.bind(this.session), sendMessage: this.session.onNewMessage.bind(this.session),
streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session), streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session),
connectionPropertyChanged: this.session.onConnectionPropertyChanged.bind(this.session), connectionPropertyChanged: this.session.onConnectionPropertyChanged.bind(this.session),

View File

@ -1184,11 +1184,7 @@ export class Session extends EventDispatcher {
logger.error('Media error: ' + JSON.stringify(event)); logger.error('Media error: ' + JSON.stringify(event));
const err = event.error; const err = event.error;
if (err) { if (err) {
this.ee.emitEvent('error-media', [ this.ee.emitEvent('error-media', [{ error: err }]);
{
error: err
}
]);
} else { } else {
logger.warn('Received undefined media error:', event); logger.warn('Received undefined media error:', event);
} }
@ -1208,6 +1204,20 @@ export class Session extends EventDispatcher {
this.ee.emitEvent('recordingStopped', [new RecordingEvent(this, 'recordingStopped', event.id, event.name, event.reason)]); this.ee.emitEvent('recordingStopped', [new RecordingEvent(this, 'recordingStopped', event.id, event.name, event.reason)]);
} }
/**
* @hidden
*/
onBroadcastStarted(): void {
this.ee.emitEvent('broadcastStarted', []);
}
/**
* @hidden
*/
onBroadcastStopped(): void {
this.ee.emitEvent('broadcastStopped', []);
}
/** /**
* @hidden * @hidden
*/ */

View File

@ -143,7 +143,7 @@ export interface SessionEventMap extends EventMap {
/** /**
* Event dispatched when the session has started being recorded. * Event dispatched when the session has started being recorded.
* *
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [OpenVidu Server configuration](/en/stable/reference-docs/openvidu-config/) * Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`) * defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
*/ */
recordingStarted: RecordingEvent; recordingStarted: RecordingEvent;
@ -151,11 +151,33 @@ export interface SessionEventMap extends EventMap {
/** /**
* Event dispatched when the session has stopped being recorded. * Event dispatched when the session has stopped being recorded.
* *
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [OpenVidu Server configuration](/en/stable/reference-docs/openvidu-config/) * Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`) * defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
*/ */
recordingStopped: RecordingEvent; recordingStopped: RecordingEvent;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when the session has started being broadcasted. See [Broadcasting to YouTube/Twitch](/en/stable/advanced-features/broadcasting/)
*/
broadcastStarted: never;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when the session has stopped being broadcasted. See [Broadcasting to YouTube/Twitch](/en/stable/advanced-features/broadcasting/)
*/
broadcastStopped: never;
/** /**
* **This feature is part of OpenVidu * **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a> * <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>

View File

@ -50,7 +50,7 @@ public class OpenViduException extends JsonRpcErrorException {
SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601), SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601),
RTMP_START_ERROR_CODE(710), DOCKER_NOT_FOUND(709), RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707), BROADCAST_START_ERROR_CODE(710), DOCKER_NOT_FOUND(709), RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707),
RECORDING_DELETE_ERROR_CODE(706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704), RECORDING_DELETE_ERROR_CODE(706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704),
RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701), RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701),

View File

@ -229,6 +229,10 @@ public class ProtocolElements {
public static final String RECORDINGSTOPPED_METHOD = "recordingStopped"; public static final String RECORDINGSTOPPED_METHOD = "recordingStopped";
public static final String RECORDINGSTOPPED_ID_PARAM = "id"; public static final String RECORDINGSTOPPED_ID_PARAM = "id";
public static final String BROADCASTSTARTED_METHOD = "broadcastStarted";
public static final String BROADCASTSTOPPED_METHOD = "broadcastStopped";
public static final String SPEECHTOTEXTMESSAGE_METHOD = "speechToTextMessage"; public static final String SPEECHTOTEXTMESSAGE_METHOD = "speechToTextMessage";
public static final String SPEECHTOTEXTMESSAGE_TIMESTAMP_PARAM = "timestamp"; public static final String SPEECHTOTEXTMESSAGE_TIMESTAMP_PARAM = "timestamp";
public static final String SPEECHTOTEXTMESSAGE_SESSIONID_PARAM = "sessionId"; public static final String SPEECHTOTEXTMESSAGE_SESSIONID_PARAM = "sessionId";

View File

@ -31,12 +31,12 @@ RUN adduser root pulse-access
# Clean # Clean
RUN apt clean && apt autoclean && apt autoremove RUN apt clean && apt autoclean && apt autoremove
COPY entrypoint.sh scripts/composed.sh scripts/composed_quick_start.sh scripts/rtmp.sh ./ COPY entrypoint.sh scripts/composed.sh scripts/composed_quick_start.sh scripts/broadcast.sh ./
COPY utils/xvfb-run-safe /usr/local/bin COPY utils/xvfb-run-safe /usr/local/bin
COPY utils/headless-chrome.sh ./ COPY utils/headless-chrome.sh ./
# Prepare scripts and folders # Prepare scripts and folders
RUN chmod +x /entrypoint.sh /composed.sh /composed_quick_start.sh /rtmp.sh /headless-chrome.sh \ RUN chmod +x /entrypoint.sh /composed.sh /composed_quick_start.sh /broadcast.sh /headless-chrome.sh \
&& chmod +x /usr/local/bin/xvfb-run-safe \ && chmod +x /usr/local/bin/xvfb-run-safe \
&& mkdir /recordings \ && mkdir /recordings \
&& chmod 777 /recordings && chmod 777 /recordings

View File

@ -1,6 +1,6 @@
#!/bin/bash -x #!/bin/bash -x
# eg: $ ./create_image.sh 109.0.5414.74-1 2.25.0 # eg: $ ./create_image.sh 109.0.5414.119-1 2.25.0
OPENVIDU_RECORDING_CHROME_VERSION=$1 # https://www.ubuntuupdates.org/package_logs?noppa=&page=1&type=ppas&vals=8# OPENVIDU_RECORDING_CHROME_VERSION=$1 # https://www.ubuntuupdates.org/package_logs?noppa=&page=1&type=ppas&vals=8#
OPENVIDU_RECORDING_DOCKER_TAG=$2 OPENVIDU_RECORDING_DOCKER_TAG=$2
docker build --rm --pull --no-cache \ docker build --rm --pull --no-cache \

View File

@ -14,8 +14,8 @@ else
./composed.sh ./composed.sh
elif [[ "${CONTAINER_WORKING_MODE}" == "COMPOSED_QUICK_START" ]]; then elif [[ "${CONTAINER_WORKING_MODE}" == "COMPOSED_QUICK_START" ]]; then
./composed_quick_start.sh ./composed_quick_start.sh
elif [[ "${CONTAINER_WORKING_MODE}" == "RTMP" ]]; then elif [[ "${CONTAINER_WORKING_MODE}" == "BROADCAST" ]]; then
./rtmp.sh ./broadcast.sh
fi fi
fi fi

View File

@ -10,14 +10,14 @@ fi
### Variables ### ### Variables ###
RTMP_URL=${RTMP_URL} BROADCAST_URL=${BROADCAST_URL}
URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo} URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
RESOLUTION=${RESOLUTION:-1920x1080} RESOLUTION=${RESOLUTION:-1920x1080}
FRAMERATE=${FRAMERATE:-25} FRAMERATE=${FRAMERATE:-25}
WIDTH="$(cut -d'x' -f1 <<<$RESOLUTION)" WIDTH="$(cut -d'x' -f1 <<<$RESOLUTION)"
HEIGHT="$(cut -d'x' -f2 <<<$RESOLUTION)" HEIGHT="$(cut -d'x' -f2 <<<$RESOLUTION)"
export RTMP_URL export BROADCAST_URL
export URL export URL
export RESOLUTION export RESOLUTION
export FRAMERATE export FRAMERATE
@ -34,9 +34,9 @@ fi
source /headless-chrome.sh source /headless-chrome.sh
### Run RTMP command ### ### Run broadcast command ###
eval "$RTMP_COMMAND" eval "$BROADCAST_COMMAND"
} 2>&1 | tee -a /tmp/container.log } 2>&1 | tee -a /tmp/container.log

View File

@ -229,12 +229,12 @@ public class Participant {
return ProtocolElements.STT_PARTICIPANT_PUBLICID.equals(this.participantPublicId); return ProtocolElements.STT_PARTICIPANT_PUBLICID.equals(this.participantPublicId);
} }
public boolean isRtmpParticipant() { public boolean isBroadcastParticipant() {
return isRecorderParticipant(); return isRecorderParticipant();
} }
public boolean isRecorderOrSttOrRtmpParticipant() { public boolean isRecorderOrSttOrBroadcastParticipant() {
return (this.isRecorderParticipant() || this.isSttParticipant() || this.isRtmpParticipant()); return (this.isRecorderParticipant() || this.isSttParticipant() || this.isBroadcastParticipant());
} }
public String getPublisherStreamId() { public String getPublisherStreamId() {

View File

@ -139,8 +139,8 @@ public class Session implements SessionInterface {
return null; return null;
} }
public boolean onlyRecorderAndOrSttAndOrRtmpParticipant() { public boolean onlyRecorderAndOrSttAndOrBroadcastParticipant() {
return this.participants.values().stream().allMatch(p -> p.isRecorderOrSttOrRtmpParticipant()); return this.participants.values().stream().allMatch(p -> p.isRecorderOrSttOrBroadcastParticipant());
} }
public int getActivePublishers() { public int getActivePublishers() {
@ -218,8 +218,8 @@ public class Session implements SessionInterface {
Set<Participant> snapshotOfActiveConnections = this.getParticipants().stream().collect(Collectors.toSet()); Set<Participant> snapshotOfActiveConnections = this.getParticipants().stream().collect(Collectors.toSet());
JsonArray jsonArray = new JsonArray(); JsonArray jsonArray = new JsonArray();
snapshotOfActiveConnections.forEach(participant -> { snapshotOfActiveConnections.forEach(participant -> {
// Filter RECORDER/STT/RTMP participants // Filter RECORDER/STT/BROADCAST participants
if (!participant.isRecorderOrSttOrRtmpParticipant()) { if (!participant.isRecorderOrSttOrBroadcastParticipant()) {
jsonArray.add(withWebrtcStats ? participant.withStatsToJson() : participant.toJson()); jsonArray.add(withWebrtcStats ? participant.withStatsToJson() : participant.toJson());
} }
}); });

View File

@ -130,16 +130,16 @@ public class SessionEventsHandler {
participantJson.add(ProtocolElements.JOINROOM_PEERSTREAMS_PARAM, streamsArray); participantJson.add(ProtocolElements.JOINROOM_PEERSTREAMS_PARAM, streamsArray);
} }
// Avoid emitting 'connectionCreated' event of existing RECORDER/STT/RTMP // Avoid emitting 'connectionCreated' event of existing RECORDER/STT/BROADCAST
// participant in openvidu-browser in newly joined participants // participant in openvidu-browser in newly joined participants
if (!existingParticipant.isRecorderOrSttOrRtmpParticipant()) { if (!existingParticipant.isRecorderOrSttOrBroadcastParticipant()) {
resultArray.add(participantJson); resultArray.add(participantJson);
} }
// If RECORDER/STT/RTMP participant has joined do NOT send 'participantJoined' // If RECORDER/STT/BROADCAST participant has joined do NOT send 'participantJoined'
// notification to existing participants. 'recordingStarted' will be sent to all // notification to existing participants. 'recordingStarted' will be sent to all
// existing participants when recorder first subscribe to a stream // existing participants when recorder first subscribe to a stream
if (!participant.isRecorderOrSttOrRtmpParticipant()) { if (!participant.isRecorderOrSttOrBroadcastParticipant()) {
JsonObject notifParams = new JsonObject(); JsonObject notifParams = new JsonObject();
// Metadata associated to new participant // Metadata associated to new participant
@ -519,8 +519,8 @@ public class SessionEventsHandler {
evictedParticipant.getParticipantPublicId()); evictedParticipant.getParticipantPublicId());
params.addProperty(ProtocolElements.PARTICIPANTEVICTED_REASON_PARAM, reason != null ? reason.name() : ""); params.addProperty(ProtocolElements.PARTICIPANTEVICTED_REASON_PARAM, reason != null ? reason.name() : "");
if (evictedParticipant.isRecorderOrSttOrRtmpParticipant()) { if (evictedParticipant.isRecorderOrSttOrBroadcastParticipant()) {
// Do not send a message when evicting RECORDER/STT/RTMP participant // Do not send a message when evicting RECORDER/STT/BROADCAST participant
rpcNotificationService.sendNotification(evictedParticipant.getParticipantPrivateId(), rpcNotificationService.sendNotification(evictedParticipant.getParticipantPrivateId(),
ProtocolElements.PARTICIPANTEVICTED_METHOD, params); ProtocolElements.PARTICIPANTEVICTED_METHOD, params);
} }
@ -719,7 +719,7 @@ public class SessionEventsHandler {
protected Set<Participant> filterParticipantsByRole(Set<OpenViduRole> roles, Set<Participant> participants) { protected Set<Participant> filterParticipantsByRole(Set<OpenViduRole> roles, Set<Participant> participants) {
return participants.stream().filter(part -> { return participants.stream().filter(part -> {
if (part.isRecorderOrSttOrRtmpParticipant()) { if (part.isRecorderOrSttOrBroadcastParticipant()) {
return false; return false;
} }
return roles.contains(part.getToken().getRole()); return roles.contains(part.getToken().getRole());

View File

@ -192,7 +192,7 @@ public abstract class SessionManager {
public abstract void onUnsubscribeFromSpeechToText(Participant participant, Integer transactionId, public abstract void onUnsubscribeFromSpeechToText(Participant participant, Integer transactionId,
String connectionId); String connectionId);
public abstract void stopRtmpIfNecessary(Session session); public abstract void stopBroadcastIfNecessary(Session session);
public void onEcho(String participantPrivateId, Integer requestId) { public void onEcho(String participantPrivateId, Integer requestId) {
sessionEventsHandler.onEcho(participantPrivateId, requestId); sessionEventsHandler.onEcho(participantPrivateId, requestId);

View File

@ -283,7 +283,7 @@ public class KurentoParticipant extends Participant {
String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher()); String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher());
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", log.info("PARTICIPANT {}: Is now receiving video from {} in room {}",
this.getParticipantPublicId(), senderName, this.session.getSessionId()); this.getParticipantPublicId(), senderName, this.session.getSessionId());
if (!silent && !this.isRecorderOrSttOrRtmpParticipant()) { if (!silent && !this.isRecorderOrSttOrBroadcastParticipant()) {
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(), endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
sender.getParticipantPublicId(), subscriber.createdAt()); sender.getParticipantPublicId(), subscriber.createdAt());
} }
@ -589,7 +589,7 @@ public class KurentoParticipant extends Participant {
} }
} }
if (!this.isRecorderOrSttOrRtmpParticipant()) { if (!this.isRecorderOrSttOrBroadcastParticipant()) {
endpointConfig.getCdr().stopSubscriber(this.getParticipantPublicId(), senderName, endpointConfig.getCdr().stopSubscriber(this.getParticipantPublicId(), senderName,
subscriber.getStreamId(), reason); subscriber.getStreamId(), reason);
} }

View File

@ -86,7 +86,7 @@ public class KurentoSession extends Session {
log.info("SESSION {}: Added participant {}", sessionId, participant); log.info("SESSION {}: Added participant {}", sessionId, participant);
if (!participant.isRecorderOrSttOrRtmpParticipant()) { if (!participant.isRecorderOrSttOrBroadcastParticipant()) {
kurentoEndpointConfig.getCdr().recordParticipantJoined(participant, sessionId); kurentoEndpointConfig.getCdr().recordParticipantJoined(participant, sessionId);
} }
} }
@ -386,7 +386,7 @@ public class KurentoSession extends Session {
public int getNumberOfWebrtcConnections() { public int getNumberOfWebrtcConnections() {
return this.getActivePublishers() return this.getActivePublishers()
+ this.participants.values().stream().filter(p -> !p.isRecorderOrSttOrRtmpParticipant()) + this.participants.values().stream().filter(p -> !p.isRecorderOrSttOrBroadcastParticipant())
.mapToInt(p -> ((KurentoParticipant) p).getSubscribers().size()).reduce(0, Integer::sum); .mapToInt(p -> ((KurentoParticipant) p).getSubscribers().size()).reduce(0, Integer::sum);
} }

View File

@ -300,7 +300,7 @@ public class KurentoSessionManager extends SessionManager {
boolean recordingParticipantLeft = remainingParticipants.size() > 0 boolean recordingParticipantLeft = remainingParticipants.size() > 0
&& remainingParticipants.stream() && remainingParticipants.stream()
.allMatch(p -> p.isRecorderOrSttOrRtmpParticipant()) .allMatch(p -> p.isRecorderOrSttOrBroadcastParticipant())
&& remainingParticipants.stream().anyMatch(p -> p.isRecorderParticipant()); && remainingParticipants.stream().anyMatch(p -> p.isRecorderParticipant());
if (recordingParticipantLeft) { if (recordingParticipantLeft) {
@ -323,13 +323,13 @@ public class KurentoSessionManager extends SessionManager {
} }
} }
boolean rtmpParticipantLeft = remainingParticipants.size() > 0 boolean broadcastParticipantLeft = remainingParticipants.size() > 0
&& remainingParticipants.stream() && remainingParticipants.stream()
.allMatch(p -> p.isRecorderOrSttOrRtmpParticipant()) .allMatch(p -> p.isRecorderOrSttOrBroadcastParticipant())
&& remainingParticipants.stream().anyMatch(p -> p.isRtmpParticipant()); && remainingParticipants.stream().anyMatch(p -> p.isBroadcastParticipant());
if (rtmpParticipantLeft) { if (broadcastParticipantLeft) {
this.stopRtmpIfNecessary(session); this.stopBroadcastIfNecessary(session);
} }
} }
} }
@ -1425,7 +1425,7 @@ public class KurentoSessionManager extends SessionManager {
} }
@Override @Override
public void stopRtmpIfNecessary(Session session) { public void stopBroadcastIfNecessary(Session session) {
} }
private io.openvidu.server.recording.Recording getActiveRecordingIfAllowedByParticipantRole( private io.openvidu.server.recording.Recording getActiveRecordingIfAllowedByParticipantRole(

View File

@ -694,8 +694,8 @@ public class RecordingManager {
return; return;
} }
if (session.getParticipants().size() == 0 if (session.getParticipants().size() == 0
|| session.onlyRecorderAndOrSttAndOrRtmpParticipant()) { || session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) {
// Close session if there are no participants connected (RECORDER/STT/RTMP do // Close session if there are no participants connected (RECORDER/STT/BROADCAST do
// not count) and publishing // not count) and publishing
log.info("Closing session {} after automatic stop of recording {}", log.info("Closing session {} after automatic stop of recording {}",
session.getSessionId(), recordingId); session.getSessionId(), recordingId);
@ -748,9 +748,9 @@ public class RecordingManager {
return false; return false;
} }
if (session.getParticipants().size() == 0 if (session.getParticipants().size() == 0
|| session.onlyRecorderAndOrSttAndOrRtmpParticipant()) { || session.onlyRecorderAndOrSttAndOrBroadcastParticipant()) {
// Close session if there are no participants connected (except for // Close session if there are no participants connected (except for
// RECORDER/STT/RTMP). This code will only be executed if recording is manually // RECORDER/STT/BROADCAST). This code will only be executed if recording is manually
// stopped during the automatic stop timeout, so the session must be also closed // stopped during the automatic stop timeout, so the session must be also closed
log.info( log.info(
"Ongoing recording of session {} was explicetly stopped within timeout for automatic recording stop. Closing session", "Ongoing recording of session {} was explicetly stopped within timeout for automatic recording stop. Closing session",