mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server, openvidu-node-client, openvidu-java-client: Add COMPOSED_QUICK_START outputMode
parent
45d3ba6078
commit
0a02ae8059
|
@ -201,7 +201,8 @@ public class OpenVidu {
|
|||
json.addProperty("hasAudio", properties.hasAudio());
|
||||
json.addProperty("hasVideo", properties.hasVideo());
|
||||
|
||||
if (Recording.OutputMode.COMPOSED.equals(properties.outputMode()) && properties.hasVideo()) {
|
||||
if ((Recording.OutputMode.COMPOSED.equals(properties.outputMode()) || (Recording.OutputMode.COMPOSED_QUICK_START.equals(properties.outputMode())))
|
||||
&& properties.hasVideo()) {
|
||||
json.addProperty("resolution", properties.resolution());
|
||||
json.addProperty("recordingLayout",
|
||||
(properties.recordingLayout() != null) ? properties.recordingLayout().name() : "");
|
||||
|
|
|
@ -74,7 +74,14 @@ public class Recording {
|
|||
/**
|
||||
* Record each stream individually
|
||||
*/
|
||||
INDIVIDUAL;
|
||||
INDIVIDUAL,
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL
|
||||
* This option is intended to keep a recorder session openned for
|
||||
* incoming recording requests to be recorded as fast as possible
|
||||
*/
|
||||
COMPOSED_QUICK_START;
|
||||
}
|
||||
|
||||
private Recording.Status status;
|
||||
|
@ -105,7 +112,7 @@ public class Recording {
|
|||
OutputMode outputMode = OutputMode.valueOf(json.get("outputMode").getAsString());
|
||||
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
||||
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
||||
if (OutputMode.COMPOSED.equals(outputMode) && hasVideo) {
|
||||
if ((OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode)) && hasVideo) {
|
||||
builder.resolution(json.get("resolution").getAsString());
|
||||
builder.recordingLayout(RecordingLayout.valueOf(json.get("recordingLayout").getAsString()));
|
||||
JsonElement customLayout = json.get("customLayout");
|
||||
|
|
|
@ -52,7 +52,7 @@ public class RecordingProperties {
|
|||
* Builder for {@link io.openvidu.java.client.RecordingProperties}
|
||||
*/
|
||||
public RecordingProperties build() {
|
||||
if (OutputMode.COMPOSED.equals(this.outputMode)) {
|
||||
if (OutputMode.COMPOSED.equals(this.outputMode) || OutputMode.COMPOSED_QUICK_START.equals(this.outputMode)) {
|
||||
this.recordingLayout = this.recordingLayout != null ? this.recordingLayout : RecordingLayout.BEST_FIT;
|
||||
this.resolution = this.resolution != null ? this.resolution : "1920x1080";
|
||||
if (RecordingLayout.CUSTOM.equals(this.recordingLayout)) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { RecordingProperties } from './RecordingProperties';
|
|||
import { Session } from './Session';
|
||||
import { SessionProperties } from './SessionProperties';
|
||||
import { RecordingLayout } from './RecordingLayout';
|
||||
import { RecordingMode } from 'RecordingMode';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
@ -143,7 +144,8 @@ export class OpenVidu {
|
|||
hasAudio: !!(properties.hasAudio),
|
||||
hasVideo: !!(properties.hasVideo)
|
||||
};
|
||||
if (data.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]) {
|
||||
if (data.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]
|
||||
|| data.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED_QUICK_START]) {
|
||||
data.resolution = !!properties.resolution ? properties.resolution : '1920x1080';
|
||||
data.recordingLayout = !!properties.recordingLayout ? properties.recordingLayout : RecordingLayout.BEST_FIT;
|
||||
if (data.recordingLayout.toString() === RecordingLayout[RecordingLayout.CUSTOM]) {
|
||||
|
|
|
@ -82,7 +82,8 @@ export class Recording {
|
|||
hasAudio: !!(json['hasAudio']),
|
||||
hasVideo: !!json['hasVideo']
|
||||
};
|
||||
if (this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]) {
|
||||
if (this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]
|
||||
|| this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED_QUICK_START]) {
|
||||
this.properties.resolution = !!(json['resolution']) ? json['resolution'] : '1920x1080';
|
||||
this.properties.recordingLayout = !!(json['recordingLayout']) ? json['recordingLayout'] : RecordingLayout.BEST_FIT;
|
||||
if (this.properties.recordingLayout.toString() === RecordingLayout[RecordingLayout.CUSTOM]) {
|
||||
|
@ -140,6 +141,9 @@ export namespace Recording {
|
|||
* Record all streams in a grid layout in a single archive
|
||||
*/
|
||||
COMPOSED = 'COMPOSED',
|
||||
|
||||
|
||||
COMPOSED_QUICK_START = 'COMPOSED_QUICK_START',
|
||||
|
||||
/**
|
||||
* Record each stream individually
|
||||
|
|
|
@ -1,109 +1,130 @@
|
|||
#!/bin/bash -x
|
||||
#!/bin/bash
|
||||
|
||||
### Variables ###
|
||||
# DEBUG MODE
|
||||
DEBUG_MODE=${DEBUG_MODE:-false}
|
||||
if [[ ${DEBUG_MODE} == true ]]; then
|
||||
DEBUG_CHROME_FLAGS="--enable-logging --v=1"
|
||||
fi
|
||||
|
||||
URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
|
||||
ONLY_VIDEO=${ONLY_VIDEO:-false}
|
||||
RESOLUTION=${RESOLUTION:-1920x1080}
|
||||
FRAMERATE=${FRAMERATE:-25}
|
||||
WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
|
||||
HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"
|
||||
VIDEO_ID=${VIDEO_ID:-video}
|
||||
VIDEO_NAME=${VIDEO_NAME:-video}
|
||||
VIDEO_FORMAT=${VIDEO_FORMAT:-mp4}
|
||||
RECORDING_JSON="${RECORDING_JSON}"
|
||||
{
|
||||
### Variables ###
|
||||
|
||||
export URL
|
||||
export ONLY_VIDEO
|
||||
export RESOLUTION
|
||||
export FRAMERATE
|
||||
export WIDTH
|
||||
export HEIGHT
|
||||
export VIDEO_ID
|
||||
export VIDEO_NAME
|
||||
export VIDEO_FORMAT
|
||||
export RECORDING_JSON
|
||||
URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
|
||||
ONLY_VIDEO=${ONLY_VIDEO:-false}
|
||||
RESOLUTION=${RESOLUTION:-1920x1080}
|
||||
FRAMERATE=${FRAMERATE:-25}
|
||||
WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
|
||||
HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"
|
||||
VIDEO_ID=${VIDEO_ID:-video}
|
||||
VIDEO_NAME=${VIDEO_NAME:-video}
|
||||
VIDEO_FORMAT=${VIDEO_FORMAT:-mp4}
|
||||
RECORDING_JSON="${RECORDING_JSON}"
|
||||
|
||||
### Store Recording json data ###
|
||||
export URL
|
||||
export ONLY_VIDEO
|
||||
export RESOLUTION
|
||||
export FRAMERATE
|
||||
export WIDTH
|
||||
export HEIGHT
|
||||
export VIDEO_ID
|
||||
export VIDEO_NAME
|
||||
export VIDEO_FORMAT
|
||||
export RECORDING_JSON
|
||||
|
||||
echo "==== Loaded Environment Variables ======================="
|
||||
env
|
||||
echo "========================================================="
|
||||
|
||||
mkdir /recordings/$VIDEO_ID
|
||||
chmod 777 /recordings/$VIDEO_ID
|
||||
echo $RECORDING_JSON > /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
### Store Recording json data ###
|
||||
|
||||
### Get a free display identificator ###
|
||||
mkdir /recordings/$VIDEO_ID
|
||||
chmod 777 /recordings/$VIDEO_ID
|
||||
echo $RECORDING_JSON > /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
DISPLAY_NUM=99
|
||||
DONE="no"
|
||||
### Get a free display identificator ###
|
||||
|
||||
while [ "$DONE" == "no" ]
|
||||
do
|
||||
out=$(xdpyinfo -display :$DISPLAY_NUM 2>&1)
|
||||
if [[ "$out" == name* ]] || [[ "$out" == Invalid* ]]
|
||||
then
|
||||
# Command succeeded; or failed with access error; display exists
|
||||
(( DISPLAY_NUM+=1 ))
|
||||
else
|
||||
# Display doesn't exist
|
||||
DONE="yes"
|
||||
DISPLAY_NUM=99
|
||||
DONE="no"
|
||||
|
||||
while [ "$DONE" == "no" ]
|
||||
do
|
||||
out=$(xdpyinfo -display :$DISPLAY_NUM 2>&1)
|
||||
if [[ "$out" == name* ]] || [[ "$out" == Invalid* ]]
|
||||
then
|
||||
# Command succeeded; or failed with access error; display exists
|
||||
(( DISPLAY_NUM+=1 ))
|
||||
else
|
||||
# Display doesn't exist
|
||||
DONE="yes"
|
||||
fi
|
||||
done
|
||||
|
||||
export DISPLAY_NUM
|
||||
|
||||
echo "First available display -> :$DISPLAY_NUM"
|
||||
echo "----------------------------------------"
|
||||
|
||||
pulseaudio -D
|
||||
|
||||
### Start Chrome in headless mode with xvfb, using the display num previously obtained ###
|
||||
|
||||
touch xvfb.log
|
||||
chmod 777 xvfb.log
|
||||
xvfb-run --server-num=${DISPLAY_NUM} --server-args="-ac -screen 0 ${RESOLUTION}x24 -noreset" google-chrome --kiosk --start-maximized --test-type --no-sandbox --disable-infobars --disable-gpu --disable-popup-blocking --window-size=$WIDTH,$HEIGHT --window-position=0,0 --no-first-run --ignore-certificate-errors --autoplay-policy=no-user-gesture-required $DEBUG_CHROME_FLAGS $URL &> xvfb.log &
|
||||
touch stop
|
||||
chmod 777 /recordings
|
||||
sleep 2
|
||||
|
||||
### Start recording with ffmpeg ###
|
||||
|
||||
if [[ "$ONLY_VIDEO" == true ]]
|
||||
then
|
||||
# Do not record audio
|
||||
<./stop ffmpeg -y -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
else
|
||||
# Record audio ("-f alsa -i pulse [...] -c:a aac")
|
||||
<./stop ffmpeg -y -f alsa -i pulse -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:a aac -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
fi
|
||||
done
|
||||
|
||||
export DISPLAY_NUM
|
||||
### Generate video report file ###
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT > /recordings/$VIDEO_ID/$VIDEO_ID.info
|
||||
|
||||
echo "First available display -> :$DISPLAY_NUM"
|
||||
echo "----------------------------------------"
|
||||
### Update Recording json data ###
|
||||
|
||||
pulseaudio -D
|
||||
TMP=$(mktemp /recordings/$VIDEO_ID/.$VIDEO_ID.XXXXXXXXXXXXXXXXXXXXXXX.json)
|
||||
INFO=$(cat /recordings/$VIDEO_ID/$VIDEO_ID.info | jq '.')
|
||||
HAS_AUDIO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "audio")')
|
||||
if [ -z "$HAS_AUDIO_AUX" ]; then HAS_AUDIO=false; else HAS_AUDIO=true; fi
|
||||
HAS_VIDEO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "video")')
|
||||
if [ -z "$HAS_VIDEO_AUX" ]; then HAS_VIDEO=false; else HAS_VIDEO=true; fi
|
||||
SIZE=$(echo $INFO | jq '.format.size | tonumber')
|
||||
DURATION=$(echo $INFO | jq '.format.duration | tonumber')
|
||||
|
||||
### Start Chrome in headless mode with xvfb, using the display num previously obtained ###
|
||||
if [[ "$HAS_AUDIO" == false && "$HAS_VIDEO" == false ]]
|
||||
then
|
||||
STATUS="failed"
|
||||
else
|
||||
STATUS="stopped"
|
||||
fi
|
||||
|
||||
touch xvfb.log
|
||||
chmod 777 xvfb.log
|
||||
xvfb-run --server-num=${DISPLAY_NUM} --server-args="-ac -screen 0 ${RESOLUTION}x24 -noreset" google-chrome --kiosk --start-maximized --test-type --no-sandbox --disable-infobars --disable-gpu --disable-popup-blocking --window-size=$WIDTH,$HEIGHT --window-position=0,0 --no-first-run --ignore-certificate-errors --autoplay-policy=no-user-gesture-required $URL &> xvfb.log &
|
||||
touch stop
|
||||
chmod 777 /recordings
|
||||
sleep 2
|
||||
jq -c -r ".hasAudio=$HAS_AUDIO | .hasVideo=$HAS_VIDEO | .duration=$DURATION | .size=$SIZE | .status=\"$STATUS\"" "/recordings/$VIDEO_ID/.recording.$VIDEO_ID" > $TMP && mv $TMP /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
### Start recording with ffmpeg ###
|
||||
### Generate video thumbnail ###
|
||||
|
||||
if [[ "$ONLY_VIDEO" == true ]]
|
||||
then
|
||||
# Do not record audio
|
||||
<./stop ffmpeg -y -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
else
|
||||
# Record audio ("-f alsa -i pulse [...] -c:a aac")
|
||||
<./stop ffmpeg -y -f alsa -i pulse -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:a aac -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
MIDDLE_TIME=$(ffmpeg -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}')
|
||||
THUMBNAIL_HEIGHT=$((480*$HEIGHT/$WIDTH))
|
||||
ffmpeg -ss $MIDDLE_TIME -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT -vframes 1 -s 480x$THUMBNAIL_HEIGHT /recordings/$VIDEO_ID/$VIDEO_ID.jpg
|
||||
|
||||
### Change permissions to all generated files ###
|
||||
|
||||
sudo chmod -R 777 /recordings/$VIDEO_ID
|
||||
|
||||
} 2>&1 | tee -a /tmp/container.log
|
||||
|
||||
if [[ ${DEBUG_MODE} == "true" ]]; then
|
||||
[[ -f /tmp/container.log ]] && cp /tmp/container.log /recordings/$VIDEO_ID/$VIDEO_ID-container.log || echo "/tmp/container.log not found"
|
||||
[[ -f ~/.config/google-chrome/chrome_debug.log ]] && cp ~/.config/google-chrome/chrome_debug.log /recordings/$VIDEO_ID/chrome_debug.log || echo "~/.config/google-chrome/chrome_debug.log"
|
||||
fi
|
||||
|
||||
### Generate video report file ###
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT > /recordings/$VIDEO_ID/$VIDEO_ID.info
|
||||
|
||||
### Update Recording json data ###
|
||||
|
||||
TMP=$(mktemp /recordings/$VIDEO_ID/.$VIDEO_ID.XXXXXXXXXXXXXXXXXXXXXXX.json)
|
||||
INFO=$(cat /recordings/$VIDEO_ID/$VIDEO_ID.info | jq '.')
|
||||
HAS_AUDIO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "audio")')
|
||||
if [ -z "$HAS_AUDIO_AUX" ]; then HAS_AUDIO=false; else HAS_AUDIO=true; fi
|
||||
HAS_VIDEO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "video")')
|
||||
if [ -z "$HAS_VIDEO_AUX" ]; then HAS_VIDEO=false; else HAS_VIDEO=true; fi
|
||||
SIZE=$(echo $INFO | jq '.format.size | tonumber')
|
||||
DURATION=$(echo $INFO | jq '.format.duration | tonumber')
|
||||
|
||||
if [[ "$HAS_AUDIO" == false && "$HAS_VIDEO" == false ]]
|
||||
then
|
||||
STATUS="failed"
|
||||
else
|
||||
STATUS="stopped"
|
||||
fi
|
||||
|
||||
jq -c -r ".hasAudio=$HAS_AUDIO | .hasVideo=$HAS_VIDEO | .duration=$DURATION | .size=$SIZE | .status=\"$STATUS\"" "/recordings/$VIDEO_ID/.recording.$VIDEO_ID" > $TMP && mv $TMP /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
### Generate video thumbnail ###
|
||||
|
||||
MIDDLE_TIME=$(ffmpeg -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}')
|
||||
THUMBNAIL_HEIGHT=$((480*$HEIGHT/$WIDTH))
|
||||
ffmpeg -ss $MIDDLE_TIME -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT -vframes 1 -s 480x$THUMBNAIL_HEIGHT /recordings/$VIDEO_ID/$VIDEO_ID.jpg
|
||||
|
||||
### Change permissions to all generated files ###
|
||||
|
||||
sudo chmod -R 777 /recordings/$VIDEO_ID
|
|
@ -1,141 +1,183 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
### Global variables ###
|
||||
RESOLUTION=${RESOLUTION:-1920x1080}
|
||||
WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
|
||||
HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"
|
||||
export RESOLUTION
|
||||
export WIDTH
|
||||
export HEIGHT
|
||||
#!/bin/bash
|
||||
|
||||
# DEBUG MODE
|
||||
# If debug mode
|
||||
DEBUG_MODE=${DEBUG_MODE:-false}
|
||||
if [[ ${DEBUG_MODE} == true ]]; then
|
||||
DEBUG_CHROME_FLAGS="--enable-logging --v=1"
|
||||
fi
|
||||
# QUICK_START_ACTION indicates wich action to perform when COMPOSED_QUICK_START mode is executed
|
||||
# Possible values are:
|
||||
# - Without parameters: Just execute all necessary configuration for xfvb and start chrome, waiting forever with a session openned
|
||||
# - --start-recording: Executes ffmpeg to record a session but don't stop chrome
|
||||
# - --stop-recording: Stops ffmpeg recording
|
||||
COMPOSED_QUICK_START_ACTION=$1
|
||||
export COMPOSED_QUICK_START_ACTION
|
||||
# - --process-recording: Process ffmpeg video and generates a metadata
|
||||
export COMPOSED_QUICK_START_ACTION=$1
|
||||
|
||||
if [[ -z "${COMPOSED_QUICK_START_ACTION}" ]]; then
|
||||
{
|
||||
### Variables ###
|
||||
export RESOLUTION=${RESOLUTION:-1920x1080}
|
||||
export URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
|
||||
export VIDEO_ID=${VIDEO_ID:-video}
|
||||
export WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
|
||||
export HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"
|
||||
export RECORDING_MODE=${RECORDING_MODE}
|
||||
|
||||
### Get a free display identificator ###
|
||||
|
||||
DISPLAY_NUM=99
|
||||
DONE="no"
|
||||
|
||||
echo "====== Loaded Environment Variables - Start Chrome ======"
|
||||
env
|
||||
echo "========================================================="
|
||||
|
||||
while [ "$DONE" == "no" ]
|
||||
do
|
||||
out=$(xdpyinfo -display :$DISPLAY_NUM 2>&1)
|
||||
if [[ "$out" == name* ]] || [[ "$out" == Invalid* ]]
|
||||
then
|
||||
# Command succeeded; or failed with access error; display exists
|
||||
(( DISPLAY_NUM+=1 ))
|
||||
else
|
||||
# Display doesn't exist
|
||||
DONE="yes"
|
||||
fi
|
||||
done
|
||||
|
||||
export DISPLAY_NUM
|
||||
echo "First available display -> :$DISPLAY_NUM"
|
||||
echo "----------------------------------------"
|
||||
|
||||
pulseaudio -D
|
||||
|
||||
### Start Chrome in headless mode with xvfb, using the display num previously obtained ###
|
||||
|
||||
touch xvfb.log
|
||||
chmod 777 xvfb.log
|
||||
xvfb-run --server-num=${DISPLAY_NUM} --server-args="-ac -screen 0 ${RESOLUTION}x24 -noreset" google-chrome --kiosk --start-maximized --test-type --no-sandbox --disable-infobars --disable-gpu --disable-popup-blocking --window-size=$WIDTH,$HEIGHT --window-position=0,0 --no-first-run --ignore-certificate-errors --autoplay-policy=no-user-gesture-required --enable-logging --v=1 $DEBUG_CHROME_FLAGS $URL &> xvfb.log &
|
||||
chmod 777 /recordings
|
||||
|
||||
### Variables ###
|
||||
URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
|
||||
export URL
|
||||
# Save Global Environment variables
|
||||
echo "export DISPLAY_NUM=$DISPLAY_NUM" > /tmp/display_num
|
||||
|
||||
### Get a free display identificator ###
|
||||
|
||||
DISPLAY_NUM=99
|
||||
DONE="no"
|
||||
|
||||
while [ "$DONE" == "no" ]
|
||||
do
|
||||
out=$(xdpyinfo -display :$DISPLAY_NUM 2>&1)
|
||||
if [[ "$out" == name* ]] || [[ "$out" == Invalid* ]]
|
||||
then
|
||||
# Command succeeded; or failed with access error; display exists
|
||||
(( DISPLAY_NUM+=1 ))
|
||||
else
|
||||
# Display doesn't exist
|
||||
DONE="yes"
|
||||
fi
|
||||
done
|
||||
} 2>&1 | tee -a /tmp/container-start.log
|
||||
|
||||
export DISPLAY_NUM
|
||||
# Save DISPLAY_NUM in a temp file to be accessible for other scripts
|
||||
echo "export DISPLAY_NUM=${DISPLAY_NUM}" >> /tmp/DISPLAY_NUM
|
||||
|
||||
echo "First available display -> :$DISPLAY_NUM"
|
||||
echo "----------------------------------------"
|
||||
|
||||
pulseaudio -D
|
||||
|
||||
### Start Chrome in headless mode with xvfb, using the display num previously obtained ###
|
||||
|
||||
touch xvfb.log
|
||||
chmod 777 xvfb.log
|
||||
xvfb-run --server-num=${DISPLAY_NUM} --server-args="-ac -screen 0 ${RESOLUTION}x24 -noreset" google-chrome --kiosk --start-maximized --test-type --no-sandbox --disable-infobars --disable-gpu --disable-popup-blocking --window-size=$WIDTH,$HEIGHT --window-position=0,0 --no-first-run --ignore-certificate-errors --autoplay-policy=no-user-gesture-required $URL &> xvfb.log &
|
||||
chmod 777 /recordings
|
||||
sleep infinity
|
||||
|
||||
|
||||
elif [[ "${COMPOSED_QUICK_START_ACTION}" == "--start-recording" ]]; then
|
||||
{
|
||||
export $(cat /tmp/display_num | xargs)
|
||||
rm -f /tmp/global_environment_vars
|
||||
|
||||
# Remove possible stop file from previous recordings
|
||||
[ -e stop ] && rm stop
|
||||
# Create stop file
|
||||
touch stop
|
||||
|
||||
source /tmp/DISPLAY_NUM
|
||||
|
||||
# Remove possible stop file from previous recordings
|
||||
[ -e stop ] && rm stop
|
||||
# Create stop file
|
||||
touch stop
|
||||
# Variables
|
||||
export RESOLUTION=${RESOLUTION:-1920x1080}
|
||||
export WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
|
||||
export HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"
|
||||
export ONLY_VIDEO=${ONLY_VIDEO:-false}
|
||||
export FRAMERATE=${FRAMERATE:-25}
|
||||
export VIDEO_ID=${VIDEO_ID:-video}
|
||||
export VIDEO_NAME=${VIDEO_NAME:-video}
|
||||
export VIDEO_FORMAT=${VIDEO_FORMAT:-mp4}
|
||||
export RECORDING_JSON="${RECORDING_JSON}"
|
||||
|
||||
echo "==== Loaded Environment Variables - Start Recording ====="
|
||||
env
|
||||
echo "========================================================="
|
||||
|
||||
### Store Recording json data ###
|
||||
|
||||
# Variables
|
||||
ONLY_VIDEO=${ONLY_VIDEO:-false}
|
||||
FRAMERATE=${FRAMERATE:-25}
|
||||
VIDEO_ID=${VIDEO_ID:-video}
|
||||
VIDEO_NAME=${VIDEO_NAME:-video}
|
||||
VIDEO_FORMAT=${VIDEO_FORMAT:-mp4}
|
||||
RECORDING_JSON="${RECORDING_JSON}"
|
||||
|
||||
export ONLY_VIDEO
|
||||
export FRAMERATE
|
||||
export VIDEO_ID
|
||||
export VIDEO_NAME
|
||||
export VIDEO_FORMAT
|
||||
export RECORDING_JSON
|
||||
|
||||
### Store Recording json data ###
|
||||
mkdir /recordings/$VIDEO_ID
|
||||
echo $RECORDING_JSON > /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
chmod 777 -R /recordings/$VIDEO_ID
|
||||
|
||||
# Save Global Environment variables
|
||||
env > /tmp/global_environment_vars
|
||||
|
||||
### Start recording with ffmpeg ###
|
||||
|
||||
mkdir /recordings/$VIDEO_ID
|
||||
chmod 777 /recordings/$VIDEO_ID
|
||||
echo $RECORDING_JSON > /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
### Start recording with ffmpeg ###
|
||||
|
||||
if [[ "$ONLY_VIDEO" == true ]]
|
||||
then
|
||||
# Do not record audio
|
||||
<./stop ffmpeg -y -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
else
|
||||
# Record audio ("-f alsa -i pulse [...] -c:a aac")
|
||||
<./stop ffmpeg -y -f alsa -i pulse -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:a aac -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
fi
|
||||
|
||||
### Generate video report file ###
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT > /recordings/$VIDEO_ID/$VIDEO_ID.info
|
||||
|
||||
### Update Recording json data ###
|
||||
|
||||
TMP=$(mktemp /recordings/$VIDEO_ID/.$VIDEO_ID.XXXXXXXXXXXXXXXXXXXXXXX.json)
|
||||
INFO=$(cat /recordings/$VIDEO_ID/$VIDEO_ID.info | jq '.')
|
||||
HAS_AUDIO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "audio")')
|
||||
if [ -z "$HAS_AUDIO_AUX" ]; then HAS_AUDIO=false; else HAS_AUDIO=true; fi
|
||||
HAS_VIDEO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "video")')
|
||||
if [ -z "$HAS_VIDEO_AUX" ]; then HAS_VIDEO=false; else HAS_VIDEO=true; fi
|
||||
SIZE=$(echo $INFO | jq '.format.size | tonumber')
|
||||
DURATION=$(echo $INFO | jq '.format.duration | tonumber')
|
||||
|
||||
if [[ "$HAS_AUDIO" == false && "$HAS_VIDEO" == false ]]
|
||||
then
|
||||
STATUS="failed"
|
||||
else
|
||||
STATUS="stopped"
|
||||
fi
|
||||
|
||||
jq -c -r ".hasAudio=$HAS_AUDIO | .hasVideo=$HAS_VIDEO | .duration=$DURATION | .size=$SIZE | .status=\"$STATUS\"" "/recordings/$VIDEO_ID/.recording.$VIDEO_ID" > $TMP && mv $TMP /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
### Generate video thumbnail ###
|
||||
|
||||
MIDDLE_TIME=$(ffmpeg -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}')
|
||||
THUMBNAIL_HEIGHT=$((480*$HEIGHT/$WIDTH))
|
||||
ffmpeg -ss $MIDDLE_TIME -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT -vframes 1 -s 480x$THUMBNAIL_HEIGHT /recordings/$VIDEO_ID/$VIDEO_ID.jpg
|
||||
|
||||
### Change permissions to all generated files ###
|
||||
|
||||
sudo chmod -R 777 /recordings/$VIDEO_ID
|
||||
if [[ "$ONLY_VIDEO" == true ]]
|
||||
then
|
||||
# Do not record audio
|
||||
<./stop ffmpeg -y -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
else
|
||||
# Record audio ("-f alsa -i pulse [...] -c:a aac")
|
||||
<./stop ffmpeg -y -f alsa -i pulse -f x11grab -draw_mouse 0 -framerate $FRAMERATE -video_size $RESOLUTION -i :$DISPLAY_NUM -c:a aac -c:v libx264 -preset ultrafast -crf 28 -refs 4 -qmin 4 -pix_fmt yuv420p -filter:v fps=$FRAMERATE "/recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
fi
|
||||
|
||||
} 2>&1 | tee -a /tmp/container-start-recording.log
|
||||
|
||||
elif [[ "${COMPOSED_QUICK_START_ACTION}" == "--stop-recording" ]]; then
|
||||
|
||||
echo 'q' > stop
|
||||
|
||||
{
|
||||
# Load global variables saved before
|
||||
export $(cat /tmp/global_environment_vars | xargs)
|
||||
|
||||
if [[ -f /recordings/$VIDEO_ID/$VIDEO_ID.jpg ]]; then
|
||||
echo "Video already recorded"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stop and wait ffmpeg process to be stopped
|
||||
FFMPEG_PID=$(pgrep ffmpeg)
|
||||
echo 'q' > stop && tail --pid=$FFMPEG_PID -f /dev/null
|
||||
|
||||
|
||||
### Generate video report file ###
|
||||
ffprobe -v quiet -print_format json -show_format -show_streams /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT > /recordings/$VIDEO_ID/$VIDEO_ID.info
|
||||
|
||||
### Change permissions to all generated files ###
|
||||
sudo chmod -R 777 /recordings/$VIDEO_ID
|
||||
|
||||
### Update Recording json data ###
|
||||
TMP=$(mktemp /recordings/$VIDEO_ID/.$VIDEO_ID.XXXXXXXXXXXXXXXXXXXXXXX.json)
|
||||
INFO=$(cat /recordings/$VIDEO_ID/$VIDEO_ID.info | jq '.')
|
||||
HAS_AUDIO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "audio")')
|
||||
if [ -z "$HAS_AUDIO_AUX" ]; then HAS_AUDIO=false; else HAS_AUDIO=true; fi
|
||||
HAS_VIDEO_AUX=$(echo $INFO | jq '.streams[] | select(.codec_type == "video")')
|
||||
if [ -z "$HAS_VIDEO_AUX" ]; then HAS_VIDEO=false; else HAS_VIDEO=true; fi
|
||||
SIZE=$(echo $INFO | jq '.format.size | tonumber')
|
||||
DURATION=$(echo $INFO | jq '.format.duration | tonumber')
|
||||
|
||||
if [[ "$HAS_AUDIO" == false && "$HAS_VIDEO" == false ]]
|
||||
then
|
||||
STATUS="failed"
|
||||
else
|
||||
STATUS="stopped"
|
||||
fi
|
||||
|
||||
jq -c -r ".hasAudio=$HAS_AUDIO | .hasVideo=$HAS_VIDEO | .duration=$DURATION | .size=$SIZE | .status=\"$STATUS\"" "/recordings/$VIDEO_ID/.recording.$VIDEO_ID" > $TMP && mv $TMP /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
rm -f $TMP
|
||||
|
||||
### Change permissions to metadata file ###
|
||||
sudo chmod 777 /recordings/$VIDEO_ID/.recording.$VIDEO_ID
|
||||
|
||||
echo "Recording finished /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT"
|
||||
|
||||
### Generate video thumbnail ###
|
||||
|
||||
MIDDLE_TIME=$(ffmpeg -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F ':' '{print ($3+$2*60+$1*3600)/2}')
|
||||
THUMBNAIL_HEIGHT=$((480*$HEIGHT/$WIDTH))
|
||||
ffmpeg -ss $MIDDLE_TIME -i /recordings/$VIDEO_ID/$VIDEO_NAME.$VIDEO_FORMAT -vframes 1 -s 480x$THUMBNAIL_HEIGHT /recordings/$VIDEO_ID/$VIDEO_ID.jpg &> /tmp/ffmpeg-thumbnail.log
|
||||
|
||||
} 2>&1 | tee -a /tmp/container-stop-recording.log
|
||||
fi
|
||||
|
||||
if [[ ${DEBUG_MODE} == "true" ]]; then
|
||||
[[ -f /tmp/container-start.log ]] && cp /tmp/container-start.log /recordings/$VIDEO_ID/$VIDEO_ID-container-start.log || echo "/tmp/container-start.log not found"
|
||||
[[ -f /tmp/container-start-recording.log ]] && cp /tmp/container-start-recording.log /recordings/$VIDEO_ID/$VIDEO_ID-container-start-recording.log || echo "/tmp/container-start-recording.log not found"
|
||||
[[ -f /tmp/container-stop-recording.log ]] && cp /tmp/container-stop-recording.log /recordings/$VIDEO_ID/$VIDEO_ID-container-stop-recording.log || echo "/tmp/container-stop-recording.log not found"
|
||||
[[ -f ~/.config/google-chrome/chrome_debug.log ]] && cp ~/.config/google-chrome/chrome_debug.log /recordings/$VIDEO_ID/chrome_debug.log || echo "~/.config/google-chrome/chrome_debug.log"
|
||||
fi
|
||||
|
||||
### Change permissions to all generated files ###
|
||||
sudo chmod -R 777 /recordings/$VIDEO_ID
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -118,6 +118,8 @@ public class OpenviduConfig {
|
|||
|
||||
private boolean openviduRecording;
|
||||
|
||||
private boolean openViduRecordingDebug;
|
||||
|
||||
private boolean openviduRecordingPublicAccess;
|
||||
|
||||
private Integer openviduRecordingAutostopTimeout;
|
||||
|
@ -224,6 +226,10 @@ public class OpenviduConfig {
|
|||
return this.openviduRecording;
|
||||
}
|
||||
|
||||
public boolean isOpenViduRecordingDebug() {
|
||||
return openViduRecordingDebug;
|
||||
}
|
||||
|
||||
public String getOpenViduRecordingPath() {
|
||||
return this.openviduRecordingPath;
|
||||
}
|
||||
|
@ -476,6 +482,7 @@ public class OpenviduConfig {
|
|||
: asFileSystemPath("OPENVIDU_CDR_PATH");
|
||||
|
||||
openviduRecording = asBoolean("OPENVIDU_RECORDING");
|
||||
openViduRecordingDebug = asBoolean("OPENVIDU_RECORDING_DEBUG");
|
||||
openviduRecordingPath = openviduRecording ? asWritableFileSystemPath("OPENVIDU_RECORDING_PATH")
|
||||
: asFileSystemPath("OPENVIDU_RECORDING_PATH");
|
||||
openviduRecordingPublicAccess = asBoolean("OPENVIDU_RECORDING_PUBLIC_ACCESS");
|
||||
|
|
|
@ -32,6 +32,7 @@ import java.util.stream.Collectors;
|
|||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import io.openvidu.java.client.Recording;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.kurento.jsonrpc.message.Request;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -545,10 +546,19 @@ public abstract class SessionManager {
|
|||
if (openviduConfig.isRecordingModuleEnabled() && stopRecording
|
||||
&& this.recordingManager.sessionIsBeingRecorded(session.getSessionId())) {
|
||||
try {
|
||||
recordingManager.stopRecording(session, null, RecordingManager.finalReason(reason));
|
||||
recordingManager.stopRecording(session, null, RecordingManager.finalReason(reason), true);
|
||||
} catch (OpenViduException e) {
|
||||
log.error("Error stopping recording of session {}: {}", session.getSessionId(), e.getMessage());
|
||||
}
|
||||
} else if(openviduConfig.isRecordingModuleEnabled() && stopRecording
|
||||
&& !this.recordingManager.sessionIsBeingRecorded(session.getSessionId())
|
||||
&& session.getSessionProperties().defaultOutputMode().equals(Recording.OutputMode.COMPOSED_QUICK_START)
|
||||
&& this.recordingManager.getStartedRecording(session.getSessionId()) != null) {
|
||||
try {
|
||||
this.recordingManager.stopComposedQuickStartContainer(session, reason);
|
||||
} catch (OpenViduException e) {
|
||||
log.error("Error stopping COMPOSED_QUICK_START container of session {}", session.getSessionId());
|
||||
}
|
||||
}
|
||||
|
||||
final String mediaNodeId = session.getMediaNodeId();
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.openvidu.java.client.*;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.kurento.client.GenericMediaElement;
|
||||
import org.kurento.client.IceCandidate;
|
||||
|
@ -47,11 +48,6 @@ import com.google.gson.JsonObject;
|
|||
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.SessionProperties;
|
||||
import io.openvidu.server.core.EndReason;
|
||||
import io.openvidu.server.core.FinalUser;
|
||||
import io.openvidu.server.core.IdentifierPrefixes;
|
||||
|
@ -144,6 +140,12 @@ public class KurentoSessionManager extends SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
// If Recording default layout is COMPOSED_QUICK_START
|
||||
Recording.OutputMode defaultOutputMode = kSession.getSessionProperties().defaultOutputMode();
|
||||
if (defaultOutputMode.equals(Recording.OutputMode.COMPOSED_QUICK_START)) {
|
||||
recordingManager.startComposedQuickStartContainer(kSession);
|
||||
}
|
||||
|
||||
if (kSession.isClosed()) {
|
||||
log.warn("'{}' is trying to join session '{}' but it is closing", participant.getParticipantPublicId(),
|
||||
sessionId);
|
||||
|
@ -214,12 +216,12 @@ public class KurentoSessionManager extends SessionManager {
|
|||
Participant p = sessionidParticipantpublicidParticipant.get(sessionId)
|
||||
.remove(participant.getParticipantPublicId());
|
||||
|
||||
if (this.openviduConfig.isTurnadminAvailable()) {
|
||||
if (p != null && this.openviduConfig.isTurnadminAvailable()) {
|
||||
this.coturnCredentialsService.deleteUser(p.getToken().getTurnCredentials().getUsername());
|
||||
}
|
||||
|
||||
// TODO: why is this necessary??
|
||||
if (insecureUsers.containsKey(p.getParticipantPrivateId())) {
|
||||
if (p != null && insecureUsers.containsKey(p.getParticipantPrivateId())) {
|
||||
boolean stillParticipant = false;
|
||||
for (Session s : sessions.values()) {
|
||||
if (!s.isClosed()
|
||||
|
@ -295,6 +297,14 @@ public class KurentoSessionManager extends SessionManager {
|
|||
"Last participant left. Starting {} seconds countdown for stopping recording of session {}",
|
||||
this.openviduConfig.getOpenviduRecordingAutostopTimeout(), sessionId);
|
||||
recordingManager.initAutomaticRecordingStopThread(session);
|
||||
|
||||
} else if (remainingParticipants.size() == 1 && openviduConfig.isRecordingModuleEnabled()
|
||||
&& MediaMode.ROUTED.equals(session.getSessionProperties().mediaMode())
|
||||
&& session.getSessionProperties().defaultOutputMode().equals(Recording.OutputMode.COMPOSED_QUICK_START)
|
||||
&& ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
|
||||
.equals(remainingParticipants.iterator().next().getParticipantPublicId())) {
|
||||
// If no recordings are active in COMPOSED_QUICK_START output mode, stop container
|
||||
recordingManager.stopComposedQuickStartContainer(session, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
package io.openvidu.server.recording.service;
|
||||
|
||||
import com.github.dockerjava.api.model.Bind;
|
||||
import com.github.dockerjava.api.model.Volume;
|
||||
import io.openvidu.client.OpenViduException;
|
||||
import io.openvidu.java.client.RecordingProperties;
|
||||
import io.openvidu.server.cdr.CallDetailRecord;
|
||||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.core.EndReason;
|
||||
import io.openvidu.server.core.Session;
|
||||
import io.openvidu.server.recording.Recording;
|
||||
import io.openvidu.server.recording.RecordingDownloader;
|
||||
import io.openvidu.server.utils.QuarantineKiller;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ComposedQuickStartRecordingService extends ComposedRecordingService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ComposedRecordingService.class);
|
||||
|
||||
public ComposedQuickStartRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader, OpenviduConfig openviduConfig, CallDetailRecord cdr, QuarantineKiller quarantineKiller) {
|
||||
super(recordingManager, recordingDownloader, openviduConfig, cdr, quarantineKiller);
|
||||
}
|
||||
|
||||
public void stopRecordingContainer(Session session, EndReason reason) {
|
||||
log.info("Stopping COMPOSED_QUICK_START of session {}. Reason: {}",
|
||||
session.getSessionId(), RecordingManager.finalReason(reason));
|
||||
|
||||
String containerId = this.sessionsContainers.get(session.getSessionId());
|
||||
|
||||
try {
|
||||
dockerManager.removeDockerContainer(containerId, true);
|
||||
} catch (Exception e) {
|
||||
log.error("Can't remove COMPOSED_QUICK_START recording container from session {}", session.getSessionId());
|
||||
}
|
||||
|
||||
containers.remove(containerId);
|
||||
sessionsContainers.remove(session.getSessionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Recording startRecordingWithVideo(Session session, Recording recording, RecordingProperties properties)
|
||||
throws OpenViduException {
|
||||
|
||||
log.info("Starting COMPOSED_QUICK_START ({}) recording {} of session {}",
|
||||
properties.hasAudio() ? "video + audio" : "audio-only", recording.getId(), recording.getSessionId());
|
||||
|
||||
List<String> envs = new ArrayList<>();
|
||||
|
||||
envs.add("DEBUG_MODE=" + openviduConfig.isOpenViduRecordingDebug());
|
||||
envs.add("RESOLUTION=" + properties.resolution());
|
||||
envs.add("ONLY_VIDEO=" + !properties.hasAudio());
|
||||
envs.add("FRAMERATE=30");
|
||||
envs.add("VIDEO_ID=" + recording.getId());
|
||||
envs.add("VIDEO_NAME=" + properties.name());
|
||||
envs.add("VIDEO_FORMAT=mp4");
|
||||
envs.add("RECORDING_JSON='" + recording.toJson().toString() + "'");
|
||||
|
||||
String containerId = this.sessionsContainers.get(session.getSessionId());
|
||||
try {
|
||||
String recordExecCommand = "";
|
||||
for(int i = 0; i < envs.size(); i++) {
|
||||
if (i > 0) {
|
||||
recordExecCommand += "&& ";
|
||||
}
|
||||
recordExecCommand += "export " + envs.get(i) + " ";
|
||||
}
|
||||
recordExecCommand += "&& ./composed_quick_start.sh --start-recording > /var/log/ffmpeg.log 2>&1 &";
|
||||
dockerManager.runCommandInContainer(containerId, recordExecCommand, 0);
|
||||
} catch (Exception e) {
|
||||
this.cleanRecordingMaps(recording);
|
||||
throw this.failStartRecording(session, recording,
|
||||
"Couldn't initialize recording container. Error: " + e.getMessage());
|
||||
}
|
||||
|
||||
this.sessionsContainers.put(session.getSessionId(), containerId);
|
||||
|
||||
try {
|
||||
this.waitForVideoFileNotEmpty(recording);
|
||||
} catch (OpenViduException e) {
|
||||
this.cleanRecordingMaps(recording);
|
||||
throw this.failStartRecording(session, recording,
|
||||
"Couldn't initialize recording container. Error: " + e.getMessage());
|
||||
}
|
||||
|
||||
return recording;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Recording stopRecordingWithVideo(Session session, Recording recording, EndReason reason, boolean hasSessionEnded) {
|
||||
log.info("Stopping COMPOSED_QUICK_START ({}) recording {} of session {}. Reason: {}",
|
||||
recording.hasAudio() ? "video + audio" : "audio-only", recording.getId(), recording.getSessionId(),
|
||||
RecordingManager.finalReason(reason));
|
||||
log.info("Container for session {} still being ready for new recordings", session.getSessionId());
|
||||
|
||||
String containerId = this.sessionsContainers.get(recording.getSessionId());
|
||||
|
||||
if (session == null) {
|
||||
log.warn(
|
||||
"Existing recording {} does not have an active session associated. This usually means a custom recording"
|
||||
+ " layout did not join a recorded participant or the recording has been automatically"
|
||||
+ " stopped after last user left and timeout passed",
|
||||
recording.getId());
|
||||
}
|
||||
|
||||
if (hasSessionEnded) {
|
||||
// Gracefully stop ffmpeg process
|
||||
try {
|
||||
dockerManager.runCommandInContainer(containerId, "./composed_quick_start.sh --stop-recording", 10);
|
||||
} catch (InterruptedException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
dockerManager.removeDockerContainer(containerId, true);
|
||||
} catch (Exception e) {
|
||||
failRecordingCompletion(recording, containerId, new OpenViduException(OpenViduException.Code.RECORDING_COMPLETION_ERROR_CODE,
|
||||
"Can't remove COMPOSED_QUICK_START recording container from session" + session.getSessionId()));
|
||||
}
|
||||
|
||||
containers.remove(containerId);
|
||||
sessionsContainers.remove(recording.getSessionId());
|
||||
} else {
|
||||
try {
|
||||
dockerManager.runCommandInContainer(containerId, "./composed_quick_start.sh --stop-recording", 10);
|
||||
} catch (InterruptedException e1) {
|
||||
cleanRecordingMaps(recording);
|
||||
log.error("Error stopping recording for session id: {}", session.getSessionId());
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
recording = updateRecordingAttributes(recording);
|
||||
|
||||
final String folderPath = this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + "/";
|
||||
final String metadataFilePath = folderPath + RecordingManager.RECORDING_ENTITY_FILE + recording.getId();
|
||||
this.sealRecordingMetadataFileAsReady(recording, recording.getSize(), recording.getDuration(),
|
||||
metadataFilePath);
|
||||
cleanRecordingMaps(recording);
|
||||
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
this.cdr.recordRecordingStatusChanged(recording, reason, timestamp, recording.getStatus());
|
||||
|
||||
if (session != null && reason != null) {
|
||||
this.recordingManager.sessionHandler.sendRecordingStoppedNotification(session, recording, reason);
|
||||
}
|
||||
|
||||
// Decrement active recordings
|
||||
// ((KurentoSession) session).getKms().getActiveRecordings().decrementAndGet();
|
||||
|
||||
return recording;
|
||||
}
|
||||
|
||||
public void runComposedQuickStartContainer(Session session) {
|
||||
// Start recording container if output mode=COMPOSED_QUICK_START
|
||||
Session recorderSession = session;
|
||||
io.openvidu.java.client.Recording.OutputMode defaultOutputMode = recorderSession.getSessionProperties().defaultOutputMode();
|
||||
if (io.openvidu.java.client.Recording.OutputMode.COMPOSED_QUICK_START.equals(defaultOutputMode)
|
||||
&& sessionsContainers.get(recorderSession.getSessionId()) == null) {
|
||||
// Retry to run if container is launched for the same session quickly after close it
|
||||
int secondsToRetry = 10;
|
||||
int secondsBetweenRetries = 1;
|
||||
int seconds = 0;
|
||||
boolean launched = false;
|
||||
while (!launched && seconds < secondsToRetry) {
|
||||
try {
|
||||
log.info("Launching COMPOSED_QUICK_START recording container for session: {}", recorderSession.getSessionId());
|
||||
runContainer(recorderSession, new RecordingProperties.Builder().name("")
|
||||
.outputMode(recorderSession.getSessionProperties().defaultOutputMode())
|
||||
.recordingLayout(recorderSession.getSessionProperties().defaultRecordingLayout())
|
||||
.customLayout(recorderSession.getSessionProperties().defaultCustomLayout()).build());
|
||||
log.info("COMPOSED_QUICK_START recording container launched for session: {}", recorderSession.getSessionId());
|
||||
launched = true;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to launch COMPOSED_QUICK_START recording container for session {}. Trying again in {} seconds", recorderSession.getSessionId(), secondsBetweenRetries);
|
||||
try {
|
||||
Thread.sleep(secondsBetweenRetries * 1000);
|
||||
} catch (InterruptedException e2) {}
|
||||
seconds++;
|
||||
} finally {
|
||||
if (seconds == secondsToRetry && !launched) {
|
||||
log.error("Error launchaing COMPOSED_QUICK_ªSTART recording container for session {}", recorderSession.getSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String runContainer(Session session, RecordingProperties properties) throws Exception {
|
||||
log.info("Starting COMPOSED_QUICK_START container for session id: {}", session.getSessionId());
|
||||
|
||||
Recording recording = new Recording(session.getSessionId(), null, properties);
|
||||
String layoutUrl = this.getLayoutUrl(recording);
|
||||
|
||||
List<String> envs = new ArrayList<>();
|
||||
envs.add("DEBUG_MODE=" + openviduConfig.isOpenViduRecordingDebug());
|
||||
envs.add("RECORDING_TYPE=COMPOSED_QUICK_START");
|
||||
envs.add("RESOLUTION=" + properties.resolution());
|
||||
envs.add("URL=" + layoutUrl);
|
||||
|
||||
log.info("Recorder connecting to url {}", layoutUrl);
|
||||
|
||||
String containerId = null;
|
||||
try {
|
||||
final String container = RecordingManager.IMAGE_NAME + ":" + RecordingManager.IMAGE_TAG;
|
||||
final String containerName = "recording_" + session.getSessionId();
|
||||
Volume volume1 = new Volume("/recordings");
|
||||
List<Volume> volumes = new ArrayList<>();
|
||||
volumes.add(volume1);
|
||||
Bind bind1 = new Bind(openviduConfig.getOpenViduRecordingPath(), volume1);
|
||||
List<Bind> binds = new ArrayList<>();
|
||||
binds.add(bind1);
|
||||
containerId = dockerManager.runContainer(container, containerName, null, volumes, binds, "host", envs, null,
|
||||
properties.shmSize(), false, null);
|
||||
containers.put(containerId, containerName);
|
||||
this.sessionsContainers.put(session.getSessionId(), containerId);
|
||||
} catch (Exception e) {
|
||||
if (containerId != null) {
|
||||
dockerManager.removeDockerContainer(containerId, true);
|
||||
containers.remove(containerId);
|
||||
sessionsContainers.remove(session.getSessionId());
|
||||
}
|
||||
log.error("Error while launchig container for COMPOSED_QUICK_START: ({})", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
return containerId;
|
||||
}
|
||||
|
||||
}
|
|
@ -63,11 +63,11 @@ public class ComposedRecordingService extends RecordingService {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ComposedRecordingService.class);
|
||||
|
||||
private Map<String, String> containers = new ConcurrentHashMap<>();
|
||||
private Map<String, String> sessionsContainers = new ConcurrentHashMap<>();
|
||||
protected Map<String, String> containers = new ConcurrentHashMap<>();
|
||||
protected Map<String, String> sessionsContainers = new ConcurrentHashMap<>();
|
||||
private Map<String, CompositeWrapper> composites = new ConcurrentHashMap<>();
|
||||
|
||||
private DockerManager dockerManager;
|
||||
protected DockerManager dockerManager;
|
||||
|
||||
public ComposedRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader,
|
||||
OpenviduConfig openviduConfig, CallDetailRecord cdr, QuarantineKiller quarantineKiller) {
|
||||
|
@ -102,18 +102,18 @@ public class ComposedRecordingService extends RecordingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason) {
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason, boolean hasSessionEnded) {
|
||||
recording = this.sealRecordingMetadataFileAsStopped(recording);
|
||||
if (recording.hasVideo()) {
|
||||
return this.stopRecordingWithVideo(session, recording, reason);
|
||||
return this.stopRecordingWithVideo(session, recording, reason, hasSessionEnded);
|
||||
} else {
|
||||
return this.stopRecordingAudioOnly(session, recording, reason, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason, long kmsDisconnectionTime) {
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason, long kmsDisconnectionTime, boolean hasSessionEnded) {
|
||||
if (recording.hasVideo()) {
|
||||
return this.stopRecordingWithVideo(session, recording, reason);
|
||||
return this.stopRecordingWithVideo(session, recording, reason, hasSessionEnded);
|
||||
} else {
|
||||
return this.stopRecordingAudioOnly(session, recording, reason, kmsDisconnectionTime);
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
compositeWrapper.disconnectPublisherEndpoint(streamId);
|
||||
}
|
||||
|
||||
private Recording startRecordingWithVideo(Session session, Recording recording, RecordingProperties properties)
|
||||
protected Recording startRecordingWithVideo(Session session, Recording recording, RecordingProperties properties)
|
||||
throws OpenViduException {
|
||||
|
||||
log.info("Starting composed ({}) recording {} of session {}",
|
||||
|
@ -152,6 +152,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
|
||||
String layoutUrl = this.getLayoutUrl(recording);
|
||||
|
||||
envs.add("DEBUG_MODE=" + openviduConfig.isOpenViduRecordingDebug());
|
||||
envs.add("URL=" + layoutUrl);
|
||||
envs.add("ONLY_VIDEO=" + !properties.hasAudio());
|
||||
envs.add("RESOLUTION=" + properties.resolution());
|
||||
|
@ -227,7 +228,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
return recording;
|
||||
}
|
||||
|
||||
private Recording stopRecordingWithVideo(Session session, Recording recording, EndReason reason) {
|
||||
protected Recording stopRecordingWithVideo(Session session, Recording recording, EndReason reason, boolean hasSessionEnded) {
|
||||
|
||||
log.info("Stopping composed ({}) recording {} of session {}. Reason: {}",
|
||||
recording.hasAudio() ? "video + audio" : "audio-only", recording.getId(), recording.getSessionId(),
|
||||
|
@ -389,7 +390,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
return finalRecordingArray[0];
|
||||
}
|
||||
|
||||
private void stopAndRemoveRecordingContainer(Recording recording, String containerId, int secondsOfWait) {
|
||||
protected void stopAndRemoveRecordingContainer(Recording recording, String containerId, int secondsOfWait) {
|
||||
// Gracefully stop ffmpeg process
|
||||
try {
|
||||
dockerManager.runCommandInContainer(containerId, "echo 'q' > stop", 0);
|
||||
|
@ -411,7 +412,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
containers.remove(containerId);
|
||||
}
|
||||
|
||||
private Recording updateRecordingAttributes(Recording recording) {
|
||||
protected Recording updateRecordingAttributes(Recording recording) {
|
||||
try {
|
||||
RecordingInfoUtils infoUtils = new RecordingInfoUtils(this.openviduConfig.getOpenViduRecordingPath()
|
||||
+ recording.getId() + "/" + recording.getId() + ".info");
|
||||
|
@ -436,7 +437,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
}
|
||||
}
|
||||
|
||||
private void waitForVideoFileNotEmpty(Recording recording) throws OpenViduException {
|
||||
protected void waitForVideoFileNotEmpty(Recording recording) throws OpenViduException {
|
||||
boolean isPresent = false;
|
||||
int i = 1;
|
||||
int timeout = 150; // Wait for 150*150 = 22500 = 22.5 seconds
|
||||
|
@ -459,7 +460,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
}
|
||||
}
|
||||
|
||||
private void failRecordingCompletion(Recording recording, String containerId, OpenViduException e)
|
||||
protected void failRecordingCompletion(Recording recording, String containerId, OpenViduException e)
|
||||
throws OpenViduException {
|
||||
recording.setStatus(io.openvidu.java.client.Recording.Status.failed);
|
||||
dockerManager.removeDockerContainer(containerId, true);
|
||||
|
@ -467,7 +468,7 @@ public class ComposedRecordingService extends RecordingService {
|
|||
throw e;
|
||||
}
|
||||
|
||||
private String getLayoutUrl(Recording recording) throws OpenViduException {
|
||||
protected String getLayoutUrl(Recording recording) throws OpenViduException {
|
||||
String secret = openviduConfig.getOpenViduSecret();
|
||||
|
||||
// Check if "customLayout" property defines a final URL
|
||||
|
|
|
@ -76,12 +76,14 @@ import io.openvidu.server.utils.CustomFileManager;
|
|||
import io.openvidu.server.utils.DockerManager;
|
||||
import io.openvidu.server.utils.JsonUtils;
|
||||
import io.openvidu.server.utils.QuarantineKiller;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
public class RecordingManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RecordingManager.class);
|
||||
|
||||
private ComposedRecordingService composedRecordingService;
|
||||
private ComposedQuickStartRecordingService composedQuickStartRecordingService;
|
||||
private SingleStreamRecordingService singleStreamRecordingService;
|
||||
private DockerManager dockerManager;
|
||||
|
||||
|
@ -159,6 +161,8 @@ public class RecordingManager {
|
|||
this.dockerManager = new DockerManager();
|
||||
this.composedRecordingService = new ComposedRecordingService(this, recordingDownloader, openviduConfig, cdr,
|
||||
quarantineKiller);
|
||||
this.composedQuickStartRecordingService = new ComposedQuickStartRecordingService(this, recordingDownloader, openviduConfig, cdr,
|
||||
quarantineKiller);
|
||||
this.singleStreamRecordingService = new SingleStreamRecordingService(this, recordingDownloader, openviduConfig,
|
||||
cdr, quarantineKiller);
|
||||
|
||||
|
@ -231,6 +235,14 @@ public class RecordingManager {
|
|||
this.checkRecordingPaths(openviduRecordingPath, openviduRecordingCustomLayout);
|
||||
}
|
||||
|
||||
public void startComposedQuickStartContainer(Session session) {
|
||||
this.composedQuickStartRecordingService.runComposedQuickStartContainer(session);
|
||||
}
|
||||
|
||||
public void stopComposedQuickStartContainer(Session session, EndReason reason) {
|
||||
this.composedQuickStartRecordingService.stopRecordingContainer(session, reason);
|
||||
}
|
||||
|
||||
public Recording startRecording(Session session, RecordingProperties properties) throws OpenViduException {
|
||||
try {
|
||||
if (session.recordingLock.tryLock(15, TimeUnit.SECONDS)) {
|
||||
|
@ -245,6 +257,9 @@ public class RecordingManager {
|
|||
case COMPOSED:
|
||||
recording = this.composedRecordingService.startRecording(session, properties);
|
||||
break;
|
||||
case COMPOSED_QUICK_START:
|
||||
recording = this.composedQuickStartRecordingService.startRecording(session, properties);
|
||||
break;
|
||||
case INDIVIDUAL:
|
||||
recording = this.singleStreamRecordingService.startRecording(session, properties);
|
||||
break;
|
||||
|
@ -287,7 +302,7 @@ public class RecordingManager {
|
|||
}
|
||||
}
|
||||
|
||||
public Recording stopRecording(Session session, String recordingId, EndReason reason) {
|
||||
public Recording stopRecording(Session session, String recordingId, EndReason reason, boolean hasSessionEnded) {
|
||||
Recording recording;
|
||||
if (session == null) {
|
||||
recording = this.startedRecordings.get(recordingId);
|
||||
|
@ -301,10 +316,13 @@ public class RecordingManager {
|
|||
|
||||
switch (recording.getOutputMode()) {
|
||||
case COMPOSED:
|
||||
recording = this.composedRecordingService.stopRecording(session, recording, reason);
|
||||
recording = this.composedRecordingService.stopRecording(session, recording, reason, hasSessionEnded);
|
||||
break;
|
||||
case COMPOSED_QUICK_START:
|
||||
recording = this.composedQuickStartRecordingService.stopRecording(session, recording, reason, hasSessionEnded);
|
||||
break;
|
||||
case INDIVIDUAL:
|
||||
recording = this.singleStreamRecordingService.stopRecording(session, recording, reason);
|
||||
recording = this.singleStreamRecordingService.stopRecording(session, recording, reason, hasSessionEnded);
|
||||
break;
|
||||
}
|
||||
this.abortAutomaticRecordingStopThread(session, reason);
|
||||
|
@ -316,7 +334,16 @@ public class RecordingManager {
|
|||
recording = this.sessionsRecordings.get(session.getSessionId());
|
||||
switch (recording.getOutputMode()) {
|
||||
case COMPOSED:
|
||||
recording = this.composedRecordingService.stopRecording(session, recording, reason, kmsDisconnectionTime);
|
||||
recording = this.composedRecordingService.stopRecording(session, recording, reason, kmsDisconnectionTime, true);
|
||||
if (recording.hasVideo()) {
|
||||
// Evict the recorder participant if composed recording with video
|
||||
this.sessionManager.evictParticipant(
|
||||
session.getParticipantByPublicId(ProtocolElements.RECORDER_PARTICIPANT_PUBLICID), null, null,
|
||||
null);
|
||||
}
|
||||
break;
|
||||
case COMPOSED_QUICK_START:
|
||||
recording = this.composedQuickStartRecordingService.stopRecording(session, recording, reason, kmsDisconnectionTime, true);
|
||||
if (recording.hasVideo()) {
|
||||
// Evict the recorder participant if composed recording with video
|
||||
this.sessionManager.evictParticipant(
|
||||
|
@ -531,7 +558,7 @@ public class RecordingManager {
|
|||
log.info(
|
||||
"Automatic stopping recording {}. There are users connected to session {}, but no one is publishing",
|
||||
recordingId, session.getSessionId());
|
||||
this.stopRecording(session, recordingId, EndReason.automaticStop);
|
||||
this.stopRecording(session, recordingId, EndReason.automaticStop, true);
|
||||
}
|
||||
} finally {
|
||||
if (!alreadyUnlocked) {
|
||||
|
|
|
@ -58,7 +58,7 @@ public abstract class RecordingService {
|
|||
|
||||
public abstract Recording startRecording(Session session, RecordingProperties properties) throws OpenViduException;
|
||||
|
||||
public abstract Recording stopRecording(Session session, Recording recording, EndReason reason);
|
||||
public abstract Recording stopRecording(Session session, Recording recording, EndReason reason, boolean hasSessionEnded);
|
||||
|
||||
/**
|
||||
* Generates metadata recording file (".recording.RECORDING_ID" JSON file to
|
||||
|
@ -177,7 +177,7 @@ public abstract class RecordingService {
|
|||
recording.setStatus(io.openvidu.java.client.Recording.Status.failed);
|
||||
this.recordingManager.startingRecordings.remove(recording.getId());
|
||||
this.recordingManager.sessionsRecordingsStarting.remove(session.getSessionId());
|
||||
this.stopRecording(session, recording, null);
|
||||
this.stopRecording(session, recording, null, true);
|
||||
return new OpenViduException(Code.RECORDING_START_ERROR_CODE, errorMessage);
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ public class SingleStreamRecordingService extends RecordingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason) {
|
||||
public Recording stopRecording(Session session, Recording recording, EndReason reason, boolean hasSessionEnded) {
|
||||
recording = this.sealRecordingMetadataFileAsStopped(recording);
|
||||
return this.stopRecording(session, recording, reason, 0);
|
||||
}
|
||||
|
|
|
@ -597,7 +597,7 @@ public class SessionRestController {
|
|||
Session session = sessionManager.getSession(recording.getSessionId());
|
||||
|
||||
Recording stoppedRecording = this.recordingManager.stopRecording(session, recording.getId(),
|
||||
EndReason.recordingStoppedByServer);
|
||||
EndReason.recordingStoppedByServer, false);
|
||||
|
||||
session.recordingManuallyStopped.set(true);
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@
|
|||
"description": "Whether to start OpenVidu Server with recording module service available or not (a Docker image will be downloaded during the first execution). Apart from setting this param to true, it is also necessary to explicitly configure sessions to be recorded",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "OPENVIDU_RECORDING_DEBUG",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, start recording service in debug mode",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "OPENVIDU_RECORDING_PATH",
|
||||
"type": "java.lang.String",
|
||||
|
|
|
@ -25,6 +25,7 @@ OPENVIDU_WEBHOOK_HEADERS=[]
|
|||
OPENVIDU_WEBHOOK_EVENTS=["sessionCreated","sessionDestroyed","participantJoined","participantLeft","webrtcConnectionCreated","webrtcConnectionDestroyed","recordingStatusChanged","filterEventDispatched","mediaNodeStatusChanged"]
|
||||
|
||||
OPENVIDU_RECORDING=false
|
||||
OPENVIDU_RECORDING_DEBUG=false
|
||||
OPENVIDU_RECORDING_VERSION=2.9.0
|
||||
OPENVIDU_RECORDING_PATH=/opt/openvidu/recordings
|
||||
OPENVIDU_RECORDING_PUBLIC_ACCESS=false
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,48 +1,48 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@angular/animations": "8.2.14",
|
||||
"@angular/cdk": "8.2.3",
|
||||
"@angular/common": "8.2.14",
|
||||
"@angular/compiler": "8.2.14",
|
||||
"@angular/core": "8.2.14",
|
||||
"@angular/flex-layout": "8.0.0-beta.27",
|
||||
"@angular/forms": "8.2.14",
|
||||
"@angular/http": "7.2.15",
|
||||
"@angular/material": "8.2.3",
|
||||
"@angular/platform-browser": "8.2.14",
|
||||
"@angular/platform-browser-dynamic": "8.2.14",
|
||||
"@angular/router": "8.2.14",
|
||||
"colormap": "2.3.1",
|
||||
"core-js": "3.4.7",
|
||||
"hammerjs": "2.0.8",
|
||||
"openvidu-browser": "2.14.0",
|
||||
"openvidu-node-client": "2.11.0",
|
||||
"rxjs": "6.5.3",
|
||||
"@angular/animations": "8.2.14",
|
||||
"@angular/cdk": "8.2.3",
|
||||
"@angular/common": "8.2.14",
|
||||
"@angular/compiler": "8.2.14",
|
||||
"@angular/core": "8.2.14",
|
||||
"@angular/flex-layout": "8.0.0-beta.27",
|
||||
"@angular/forms": "8.2.14",
|
||||
"@angular/http": "7.2.15",
|
||||
"@angular/material": "8.2.3",
|
||||
"@angular/platform-browser": "8.2.14",
|
||||
"@angular/platform-browser-dynamic": "8.2.14",
|
||||
"@angular/router": "8.2.14",
|
||||
"colormap": "2.3.1",
|
||||
"core-js": "3.4.7",
|
||||
"hammerjs": "2.0.8",
|
||||
"openvidu-browser": "2.14.0",
|
||||
"openvidu-node-client": "2.11.0",
|
||||
"rxjs": "6.5.3",
|
||||
"zone.js": "0.10.2"
|
||||
},
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.803.20",
|
||||
"@angular/cli": "8.3.20",
|
||||
"@angular/compiler-cli": "8.2.14",
|
||||
"@angular/language-service": "8.2.14",
|
||||
"@types/jasmine": "3.5.0",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/node": "12.12.14",
|
||||
"codelyzer": "5.2.0",
|
||||
"ts-node": "8.5.4",
|
||||
"tslint": "5.20.1",
|
||||
"@angular-devkit/build-angular": "0.803.20",
|
||||
"@angular/cli": "8.3.20",
|
||||
"@angular/compiler-cli": "8.2.14",
|
||||
"@angular/language-service": "8.2.14",
|
||||
"@types/jasmine": "3.5.0",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/node": "12.12.14",
|
||||
"codelyzer": "5.2.0",
|
||||
"ts-node": "8.5.4",
|
||||
"tslint": "5.20.1",
|
||||
"typescript": "3.5.3"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "openvidu-testapp",
|
||||
"private": true,
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "openvidu-testapp",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "ng build",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"test": "ng test"
|
||||
},
|
||||
},
|
||||
"version": "2.14.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue