Merge pull request #14911 from GuiLeme/issue-9789

This commit is contained in:
Gustavo Trott 2022-05-10 13:24:11 -03:00 committed by GitHub
commit 14815184e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 313 additions and 44 deletions

View File

@ -0,0 +1,34 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
class MeetingConfigurationEvent extends StarterConfigurationEvent {
import MeetingConfigurationEvent._
setEvent("MeetingConfigurationEvent")
def setWebcamsOnlyForModerator(webcamsOnlyForModerator: Boolean) {
eventMap.put(WEBCAMS_ONLY_FOR_MODERATOR, webcamsOnlyForModerator.toString)
}
}
object MeetingConfigurationEvent {
val WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator"
}

View File

@ -0,0 +1,24 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
trait StarterConfigurationEvent extends RecordEvent {
setModule("CONFIG")
}

View File

@ -116,6 +116,7 @@ class RedisRecorderActor(
case m: RecordStatusResetSysMsg => handleRecordStatusResetSysMsg(m)
case m: WebcamsOnlyForModeratorChangedEvtMsg => handleWebcamsOnlyForModeratorChangedEvtMsg(m)
case m: MeetingEndingEvtMsg => handleEndAndKickAllSysMsg(m)
case m: MeetingCreatedEvtMsg => handleStarterConfigurations(m)
// Recording
case m: RecordingChapterBreakSysMsg => handleRecordingChapterBreakSysMsg(m)
@ -622,4 +623,10 @@ class RedisRecorderActor(
log.error("recording database is not available.")
}
private def handleStarterConfigurations(msg: MeetingCreatedEvtMsg): Unit = {
val ev = new MeetingConfigurationEvent()
ev.setWebcamsOnlyForModerator(msg.body.props.usersProp.webcamsOnlyForModerator)
record(msg.body.props.meetingProp.intId, ev.toMap().asJava)
}
}

View File

@ -60,6 +60,7 @@ import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessa
import org.bigbluebutton.api.messaging.messages.*;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.api2.domain.UploadedTrack;
import org.bigbluebutton.common2.msgs.MeetingCreatedEvtMsg;
import org.bigbluebutton.common2.redis.RedisStorageService;
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
import org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier;

View File

@ -112,8 +112,60 @@ module BigBlueButton
start_events
end
def self.to_boolean(obj)
return obj.to_s.downcase == "true"
end
def self.is_user_moderator(user_id, list_user_info)
user_role = list_user_info[user_id]
if !user_role.nil?
if user_role == "MODERATOR"
return true
else
return false
end
end
return false
end
def self.get_id_from_filename(filename)
return filename.split("/")[-1].split("-")[1]
end
def self.extract_filename_from_userId(userId, filenames_list)
filename_return = ""
filenames_list.each do |filename|
if !filename.match(userId).nil?
filename_return = filename
end
end
return filename_return
end
def self.process_webcamsOnlyForModerator(list_user_info, active_videos, inactive_videos, webcamsOnlyForModerator)
if webcamsOnlyForModerator
list_user_info.each do |user_id, user_role|
# If the user is a viewer:
if !BigBlueButton::Events.is_user_moderator(user_id, list_user_info)
filename = BigBlueButton::Events.extract_filename_from_userId(user_id, active_videos)
if filename != ""
active_videos.delete(filename)
inactive_videos << filename
end
end
end
else
# If the WebcamsOnlyForModerator is false, all previously inactive videos will become active
inactive_videos.each do |filename|
active_videos << filename
end
inactive_videos.clear
end
end
# Build a webcam EDL
def self.create_webcam_edl(events, archive_dir)
def self.create_webcam_edl(events, archive_dir, show_moderator_viewpoint)
recording = events.at_xpath('/recording')
meeting_id = recording['meeting_id']
event = events.at_xpath('/recording/event[position()=1]')
@ -125,57 +177,203 @@ module BigBlueButton
videos = {}
active_videos = []
inactive_videos = []
video_edl = []
video_edl << {
:timestamp => 0,
:areas => { :webcam => [] }
}
events.xpath('/recording/event[@module="WEBCAM" or (@module="bbb-webrtc-sfu" and (@eventname="StartWebRTCShareEvent" or @eventname="StopWebRTCShareEvent"))]').each do |event|
timestamp = event['timestamp'].to_i - initial_timestamp
# Determine the video filename
case event['eventname']
when 'StartWebcamShareEvent', 'StopWebcamShareEvent'
stream = event.at_xpath('stream').text
filename = "#{video_dir}/#{stream}.flv"
when 'StartWebRTCShareEvent', 'StopWebRTCShareEvent'
uri = event.at_xpath('filename').text
filename = "#{video_dir}/#{File.basename(uri)}"
list_user_info = {}
webcamsOnlyForModerator = false
if show_moderator_viewpoint
events.xpath('/recording/event[@module="WEBCAM" or (@module="bbb-webrtc-sfu" and (@eventname="StartWebRTCShareEvent" or @eventname="StopWebRTCShareEvent"))]').each do |event|
timestamp = event['timestamp'].to_i - initial_timestamp
# Determine the video filename
case event['eventname']
when 'StartWebcamShareEvent', 'StopWebcamShareEvent'
stream = event.at_xpath('stream').text
filename = "#{video_dir}/#{stream}.flv"
when 'StartWebRTCShareEvent', 'StopWebRTCShareEvent'
uri = event.at_xpath('filename').text
filename = "#{video_dir}/#{File.basename(uri)}"
end
raise "Couldn't determine webcam filename" if filename.nil?
# Add the video to the EDL
case event['eventname']
when 'StartWebcamShareEvent', 'StartWebRTCShareEvent'
videos[filename] = { :timestamp => timestamp }
active_videos << filename
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp]
}
end
video_edl << edl_entry
when 'StopWebcamShareEvent', 'StopWebRTCShareEvent'
active_videos.delete(filename)
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp]
}
end
video_edl << edl_entry
end
end
raise "Couldn't determine webcam filename" if filename.nil?
else
events.xpath('/recording/event[@module="WEBCAM" or (@module="bbb-webrtc-sfu" and (@eventname="StartWebRTCShareEvent" or @eventname="StopWebRTCShareEvent")) or (@module="PARTICIPANT" and (@eventname="ParticipantStatusChangeEvent" or @eventname="ParticipantJoinEvent")) or @eventname="WebcamsOnlyForModeratorEvent" or @eventname="MeetingConfigurationEvent"]').each do |event|
timestamp = event['timestamp'].to_i - initial_timestamp
# Add the video to the EDL
case event['eventname']
when 'StartWebcamShareEvent', 'StartWebRTCShareEvent'
videos[filename] = { :timestamp => timestamp }
active_videos << filename
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp]
}
# Determine the video filename if event is as the following
case event['eventname']
when 'StartWebcamShareEvent', 'StopWebcamShareEvent'
stream = event.at_xpath('stream').text
filename = "#{video_dir}/#{stream}.flv"
when 'StartWebRTCShareEvent', 'StopWebRTCShareEvent'
uri = event.at_xpath('filename').text
filename = "#{video_dir}/#{File.basename(uri)}"
end
video_edl << edl_entry
when 'StopWebcamShareEvent', 'StopWebRTCShareEvent'
active_videos.delete(filename)
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp]
# Add the video to the EDL
case event['eventname']
when 'StartWebcamShareEvent', 'StartWebRTCShareEvent'
userId = BigBlueButton::Events.get_id_from_filename(filename)
is_in_forbidden_period = webcamsOnlyForModerator
if (!is_in_forbidden_period) || (is_in_forbidden_period && BigBlueButton::Events.is_user_moderator(userId, list_user_info))
videos[filename] = { :timestamp => timestamp }
active_videos << filename
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp],
:user_id => BigBlueButton::Events.get_id_from_filename(filename)
}
end
video_edl << edl_entry
elsif is_in_forbidden_period && !BigBlueButton::Events.is_user_moderator(userId, list_user_info)
inactive_videos << filename
videos[filename] = { :timestamp => timestamp }
end
when 'StopWebcamShareEvent', 'StopWebRTCShareEvent'
userId = BigBlueButton::Events.get_id_from_filename(filename)
is_in_forbidden_period = webcamsOnlyForModerator
if (!is_in_forbidden_period) || (is_in_forbidden_period && BigBlueButton::Events.is_user_moderator(userId, list_user_info))
active_videos.delete(filename)
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp],
:user_id => BigBlueButton::Events.get_id_from_filename(filename)
}
end
video_edl << edl_entry
elsif is_in_forbidden_period && !BigBlueButton::Events.is_user_moderator(userId, list_user_info)
inactive_videos.delete(filename)
end
when "ParticipantJoinEvent"
user_id = event.at_xpath('userId').text
list_user_info[user_id] = event.at_xpath('role').text
when "ParticipantStatusChangeEvent"
is_in_forbidden_period = webcamsOnlyForModerator
userId = ""
filename_to_add = ""
if event.at_xpath('status').text == "role"
userId = event.at_xpath('userId').text
if is_in_forbidden_period && event.at_xpath('value').text == "MODERATOR"
filename_to_add = BigBlueButton::Events.extract_filename_from_userId(userId, inactive_videos)
if filename_to_add != ""
inactive_videos.delete(filename_to_add)
active_videos << filename_to_add
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp],
:user_id => userId
}
end
video_edl << edl_entry
end
elsif is_in_forbidden_period && event.at_xpath('value').text == "VIEWER"
filename_to_add = BigBlueButton::Events.extract_filename_from_userId(userId, active_videos)
if filename != ""
active_videos.delete(filename_to_add)
inactive_videos << filename_to_add
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp],
:user_id => userId
}
end
video_edl << edl_entry
end
end
user_id = event.at_xpath('userId').text
list_user_info[user_id] = event.at_xpath('value').text
end
when "MeetingConfigurationEvent"
webcamsOnlyForModerator = BigBlueButton::Events.to_boolean(event.at_xpath('webcamsOnlyForModerator').text)
when "WebcamsOnlyForModeratorEvent"
# Change active and inactive videos.
BigBlueButton::Events.process_webcamsOnlyForModerator(list_user_info, active_videos, inactive_videos, BigBlueButton::Events.to_boolean(event.at_xpath("webcamsOnlyForModerator").text))
edl_entry = {
:timestamp => timestamp,
:areas => { :webcam => [] }
}
active_videos.each do |filename|
edl_entry[:areas][:webcam] << {
:filename => filename,
:timestamp => timestamp - videos[filename][:timestamp],
:user_id => userId
}
end
video_edl << edl_entry
webcamsOnlyForModerator = BigBlueButton::Events.to_boolean(event.at_xpath('webcamsOnlyForModerator').text)
end
video_edl << edl_entry
end
end

View File

@ -28,7 +28,7 @@ require File.expand_path('../../edl', __FILE__)
module BigBlueButton
def BigBlueButton.process_webcam_videos(target_dir, raw_archive_dir, output_width, output_height, output_framerate, audio_offset, processed_audio_file, video_formats=['webm'])
def BigBlueButton.process_webcam_videos(target_dir, raw_archive_dir, output_width, output_height, output_framerate, audio_offset, processed_audio_file, video_formats=['webm'], show_moderator_viewpoint=false)
BigBlueButton.logger.info("Processing webcam videos")
# raw_archive_dir already contains meeting_id
@ -38,7 +38,7 @@ module BigBlueButton
start_time = BigBlueButton::Events.first_event_timestamp(events)
end_time = BigBlueButton::Events.last_event_timestamp(events)
webcam_edl = BigBlueButton::Events.create_webcam_edl(
events, raw_archive_dir)
events, raw_archive_dir, show_moderator_viewpoint)
user_video_edl = BigBlueButton::Events.edl_match_recording_marks_video(
webcam_edl, events, start_time, end_time)
BigBlueButton::EDL::Video.dump(user_video_edl)

View File

@ -40,6 +40,11 @@ anonymize_chat: false
# meta param: meta_bbb-anonymize-chat-moderators (true/false)
anonymize_chat_moderators: false
# By default, recordings assume the Viewer viewpoint (with all locks)
# so when webcamsOnlyForModerator=true, only moderators webcams are included in recordings.
# Use this option to ignore locks and assume Moderator viewpoint instead.
show_moderator_viewpoint: false
# Sequence of recording steps. Keys are the current step, values
# are the next step(s). Examples:
# current_step: next_step

View File

@ -225,7 +225,7 @@ unless FileTest.directory?(target_dir)
webcam_framerate = 15 if webcam_framerate.nil?
processed_audio_file = BigBlueButton::AudioProcessor.get_processed_audio_file(raw_archive_dir, "#{target_dir}/audio")
BigBlueButton.process_webcam_videos(target_dir, raw_archive_dir, webcam_width, webcam_height, webcam_framerate, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats'])
BigBlueButton.process_webcam_videos(target_dir, raw_archive_dir, webcam_width, webcam_height, webcam_framerate, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats'], props['show_moderator_viewpoint'])
end
if !Dir["#{raw_archive_dir}/deskshare/*"].empty? && presentation_props['include_deskshare']

View File

@ -84,7 +84,7 @@ begin
logger.info "Generating video events list"
# Webcams
webcam_edl = BigBlueButton::Events.create_webcam_edl(events, raw_archive_dir)
webcam_edl = BigBlueButton::Events.create_webcam_edl(events, raw_archive_dir, props['show_moderator_viewpoint'])
logger.debug "Webcam EDL:"
BigBlueButton::EDL::Video.dump(webcam_edl)