diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index 4b1ccec93d29..e630b6e3f588 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -891,6 +891,18 @@ def self.get_external_video_events(events_xml) s = { :timestamp => event['timestamp'].to_i } external_videos_events << s end + # See: https://github.com/bigbluebutton/bbb-playback/pull/127 + # You need to directly modify the script /usr/local/bigbluebutton/core/lib/recordandplayback/generators/events.rb + events_xml.xpath("recording/event[@eventname='UpdateExternalVideoRecordEvent']").each do |event| + s = { + :timestamp => event['timestamp'].to_i, + :rate => event.at_xpath("rate").text.to_f, + :state => event.at_xpath("state").text.to_i, + :status => event.at_xpath("status").text, + :time => event.at_xpath("time").text.to_f, + } + external_videos_events << s + end external_videos_events.sort_by {|a| a[:timestamp]} end @@ -913,10 +925,15 @@ def self.get_start_and_stop_rec_events(events_xml, allow_empty_events=false) def self.get_start_and_stop_external_video_events(events_xml) BigBlueButton.logger.info "Getting start and stop externalvideo events" external_video_events = BigBlueButton::Events.get_external_video_events(events_xml) - if external_video_events.size.odd? + n_start_and_stop_events = 0 + external_video_events.each do |e| + n_start_and_stop_events += 1 unless e[:status] + end + if n_start_and_stop_events.odd? # user did not click to stop external video before ending meeting external_video_events << { :timestamp => BigBlueButton::Events.last_event_timestamp(events_xml) } end + #BigBlueButton.logger.info "get_start_and_stop_external_video_events: #{external_video_events.sort_by {|a| a[:timestamp]}}" external_video_events.sort_by {|a| a[:timestamp]} end @@ -935,19 +952,31 @@ def self.match_start_and_stop_rec_events(rec_events) matched_rec_events end - # Match external video start and stop events - def self.match_start_and_stop_external_video_events(external_video_events) + # Match external video start, update, and stop events + def self.match_all_external_video_events(external_video_events) BigBlueButton.logger.info ("Matching external video events") matched_external_video_events = [] - external_video_events.each_with_index do |evt,i| - if i.even? + external_video_events.each do |evt| + if evt[:status] # Update + matched_external_video_events[-1][:updates] << { + :timestamp => evt[:timestamp], + :rate => evt[:rate], + :state => evt[:state], + :status => evt[:status], + :time => evt[:time] + } + elsif evt[:external_video_url] # Start matched_external_video_events << { :start_timestamp => evt[:timestamp], - :stop_timestamp => external_video_events[i + 1][:timestamp], :external_video_url => evt[:external_video_url], + :updates => [] } + else # Stop (sometimes it occurrs without Start? -> [if e] is added) + e = matched_external_video_events[-1] + e[:stop_timestamp] = evt[:timestamp] if e end end + #BigBlueButton.logger.info (match_all_external_video_events: "#{matched_external_video_events}") matched_external_video_events end diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 8101ba5bca4a..0ac1018fd237 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -10,6 +10,10 @@ deskshare_output_framerate: 5 # audio_offset = 1200 means that the audio will be delayed by 1200ms audio_offset: 0 include_deskshare: true +# you need to directly modify /usr/local/bigbluebutton/core/scripts/presentation.yml, +# or overwrite it by /etc/bigbluebutton/recording/presentation.yml +# without this parameter, the external_videos.png file won't be generated and the thumbnail disappears in the player. +include_external_videos: true # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 4d8cf65dea71..656519150e55 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -838,12 +838,16 @@ def events_parse_clear(shapes, event, current_presentation, current_slide, times end end +# Changes must be directly applied to /usr/local/bigbluebutton/core/scripts/publish/presentation.rb def events_get_image_info(slide, tldraw) slide_deskshare = slide[:deskshare] + slide_external_videos = slide[:external_videos] slide_presentation = slide[:presentation] if slide_deskshare slide[:src] = 'presentation/deskshare.png' + elsif slide_external_videos + slide[:src] = 'presentation/externalVideos.png' elsif slide_presentation == '' slide[:src] = 'presentation/logo.png' else @@ -862,7 +866,7 @@ def events_get_image_info(slide, tldraw) File.write(image_path, '') else command = \ - if slide_deskshare + if slide_deskshare || slide_external_videos ['convert', '-size', "#{@presentation_props['deskshare_output_width']}x#{@presentation_props['deskshare_output_height']}", 'xc:transparent', '-background', 'transparent', image_path,] else @@ -957,6 +961,15 @@ def process_presentation(package_dir) slide_changed = true end + when 'StartExternalVideoRecordEvent' + external_videos = slide_changed = true if @presentation_props['include_external_videos'] + + when 'StopExternalVideoRecordEvent' + if @presentation_props['include_external_videos'] + external_videos = false + slide_changed = true + end + when 'AddShapeEvent', 'ModifyTextEvent' events_parse_shape(shapes, event, current_presentation, current_slide, timestamp) @@ -997,7 +1010,8 @@ def process_presentation(package_dir) if slide && (slide[:presentation] == current_presentation) && (slide[:slide] == current_slide) && - (slide[:deskshare] == deskshare) + (slide[:deskshare] == deskshare) && + (slide[:external_videos] == external_videos) BigBlueButton.logger.info('Presentation/Slide: skipping, no changes') else if slide @@ -1011,6 +1025,7 @@ def process_presentation(package_dir) slide: current_slide, in: timestamp, deskshare: deskshare, + external_videos: external_videos, } events_get_image_info(slide, tldraw) slides << slide @@ -1261,7 +1276,7 @@ def process_external_video_events(_events, package_dir) BigBlueButton.logger.info('Processing external video events') # Retrieve external video events - external_video_events = BigBlueButton::Events.match_start_and_stop_external_video_events( + external_video_events = BigBlueButton::Events.match_all_external_video_events( BigBlueButton::Events.get_start_and_stop_external_video_events(@doc) ) @@ -1270,14 +1285,17 @@ def process_external_video_events(_events, package_dir) external_video_events.each do |event| BigBlueButton.logger.info("Processing rec event #{re} and external video event #{event}") start_timestamp = event[:start_timestamp] + stop_timestamp = event[:stop_timestamp] timestamp = (translate_timestamp(start_timestamp) / 1000).to_i # do not add same external_video twice next if external_videos.find { |ev| ev[:timestamp] == timestamp } re_start_timestamp = re[:start_timestamp] re_stop_timestamp = re[:stop_timestamp] - next unless ((start_timestamp >= re_start_timestamp) && (start_timestamp <= re_stop_timestamp)) || - ((start_timestamp < re_start_timestamp) && (re_stop_timestamp >= re_start_timestamp)) + next unless ((start_timestamp >= re_start_timestamp) && (start_timestamp < re_stop_timestamp)) || + ((stop_timestamp > re_start_timestamp) && (stop_timestamp <= re_stop_timestamp)) || + ((start_timestamp <= re_start_timestamp) && (stop_timestamp >= re_stop_timestamp) && + (re_stop_timestamp > re_start_timestamp)) external_videos << { timestamp: timestamp, @@ -1287,6 +1305,58 @@ def process_external_video_events(_events, package_dir) end generate_json_file(package_dir, 'external_videos.json', external_videos) + + # Generate external_videos.xml for playback video within a presentation + # See: https://github.com/bigbluebutton/bbb-playback/pull/127 + # You need to directly modify the script /usr/local/bigbluebutton/core/scripts/publish/presentation.rb + external_videos_play = [] + @rec_events.each do |re| + external_video_events.each do |event| + start_timestamp = event[:start_timestamp] + stop_timestamp = event[:stop_timestamp] + # do not add same external_video twice + next if external_videos_play.find { |ev| ev[:start_timestamp] == start_timestamp } + + re_start_timestamp = re[:start_timestamp] + re_stop_timestamp = re[:stop_timestamp] + #next unless ((start_timestamp >= re_start_timestamp) && (start_timestamp <= re_stop_timestamp)) || + # ((start_timestamp < re_start_timestamp || stop_timestamp > re_stop_timestamp) && (re_stop_timestamp >= re_start_timestamp)) + next unless ((start_timestamp >= re_start_timestamp) && (start_timestamp < re_stop_timestamp)) || + ((stop_timestamp > re_start_timestamp) && (stop_timestamp <= re_stop_timestamp)) || + ((start_timestamp <= re_start_timestamp) && (stop_timestamp >= re_stop_timestamp) && + (re_stop_timestamp > re_start_timestamp)) + + updates = [] + event[:updates].each do |update| + update[:timestamp] = (translate_timestamp(update[:timestamp]) / 1000) + update[:type] = update[:status] + update.delete(:status) + update[:playing] = update[:state] == 0 ? false : true + update.delete(:state) + updates << update + end + + external_videos_play << { + start_timestamp: (translate_timestamp(event[:start_timestamp]) / 1000), + stop_timestamp: (translate_timestamp(event[:stop_timestamp]) / 1000), + url: event[:external_video_url], + updates: updates + } + end + end + + xml_object = Nokogiri::XML::Builder.new do |xml| + xml.recording(:id => "external_videos_events") do + external_videos_play.each do |video| + xml.video(:start_timestamp => video[:start_timestamp], :stop_timestamp => video[:stop_timestamp], :url => video[:url]) do + video[:updates].each do |update| + xml.event(update) + end + end + end + end + end + File.open("#{package_dir}/external_videos.xml", 'w') { |f| f.puts(Nokogiri::XML(xml_object.to_xml, nil, 'utf-8').to_xml) } end def generate_done_or_fail_file(success)