diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/MeetingConfigurationEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/MeetingConfigurationEvent.scala
new file mode 100755
index 0000000000..917a32a632
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/MeetingConfigurationEvent.scala
@@ -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 .
+ *
+ */
+
+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"
+}
\ No newline at end of file
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StarterConfigurationEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StarterConfigurationEvent.scala
new file mode 100755
index 0000000000..b452bb8163
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StarterConfigurationEvent.scala
@@ -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 .
+ *
+ */
+
+package org.bigbluebutton.core.record.events
+
+trait StarterConfigurationEvent extends RecordEvent {
+ setModule("CONFIG")
+}
\ No newline at end of file
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
index a106261155..d0bbd0a395 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
@@ -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)
+ }
+
}
diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java
index 9ebf1cd9dc..8aa872c7f8 100755
--- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java
+++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java
@@ -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;
diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb
index f25c190e25..97beb67cdf 100755
--- a/record-and-playback/core/lib/recordandplayback/generators/events.rb
+++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb
@@ -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
diff --git a/record-and-playback/core/lib/recordandplayback/generators/video.rb b/record-and-playback/core/lib/recordandplayback/generators/video.rb
index 76e8ecee2d..c7a0ac5a18 100755
--- a/record-and-playback/core/lib/recordandplayback/generators/video.rb
+++ b/record-and-playback/core/lib/recordandplayback/generators/video.rb
@@ -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)
diff --git a/record-and-playback/core/scripts/bigbluebutton.yml b/record-and-playback/core/scripts/bigbluebutton.yml
index 74a75217e6..3568e5eb67 100755
--- a/record-and-playback/core/scripts/bigbluebutton.yml
+++ b/record-and-playback/core/scripts/bigbluebutton.yml
@@ -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
diff --git a/record-and-playback/presentation/scripts/process/presentation.rb b/record-and-playback/presentation/scripts/process/presentation.rb
index 06878e5e26..aa64702af2 100755
--- a/record-and-playback/presentation/scripts/process/presentation.rb
+++ b/record-and-playback/presentation/scripts/process/presentation.rb
@@ -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']
diff --git a/record-and-playback/screenshare/scripts/process/screenshare.rb b/record-and-playback/screenshare/scripts/process/screenshare.rb
index 5716374dc8..a093b7765e 100644
--- a/record-and-playback/screenshare/scripts/process/screenshare.rb
+++ b/record-and-playback/screenshare/scripts/process/screenshare.rb
@@ -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)