From fda201f94da20e35e0a0d47317a4f345357823aa Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:50:07 +0900 Subject: [PATCH 01/11] Update events.rb --- .../recordandplayback/generators/events.rb | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index e3c7430230b8..27927c57791f 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -861,10 +861,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 @@ -883,19 +888,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] + 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] 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 + e = matched_external_video_events[-1] + e[:stop_timestamp] = evt[:timestamp] end end + #BigBlueButton.logger.info (match_all_external_video_events: "#{matched_external_video_events}") matched_external_video_events end From b4d303fe9e5230b669b9b9d15f0b63ca7547df9c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:50:59 +0900 Subject: [PATCH 02/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 8101ba5bca4a..fceea7c1db09 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -10,6 +10,7 @@ deskshare_output_framerate: 5 # audio_offset = 1200 means that the audio will be delayed by 1200ms audio_offset: 0 include_deskshare: true +include_external_videos: true # For PRODUCTION publish_dir: /var/bigbluebutton/published/presentation From 01cb80b6e173d885a548c3f1224a18d8f4715262 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:57:25 +0900 Subject: [PATCH 03/11] Update presentation.rb --- .../scripts/publish/presentation.rb | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 03ba75d96763..71c89fbdcffc 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -824,10 +824,13 @@ def events_parse_clear(shapes, event, current_presentation, current_slide, times 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 @@ -843,7 +846,7 @@ def events_get_image_info(slide, tldraw) # Emergency last-ditch blank image creation FileUtils.mkdir_p(File.dirname(image_path)) 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 @@ -937,6 +940,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) @@ -977,7 +989,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 @@ -991,6 +1004,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 @@ -1219,7 +1233,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) ) @@ -1228,14 +1242,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, @@ -1245,6 +1262,57 @@ 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) From 6edbc02e6b47e06c1802d0329cb9f8cf6d0ae917 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:58:24 +0900 Subject: [PATCH 04/11] Update presentation.rb --- record-and-playback/presentation/scripts/publish/presentation.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 71c89fbdcffc..1e990338c2ea 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -822,6 +822,7 @@ 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] From 05644fbfd5604314757cb08953562ea32a3bc52a Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:58:53 +0900 Subject: [PATCH 05/11] Update presentation.yml --- record-and-playback/presentation/scripts/presentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index fceea7c1db09..7f559862c06c 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -10,6 +10,7 @@ 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 include_external_videos: true # For PRODUCTION From 13c34d6ce4901de05937acd096df60c57b6f5eb6 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Sun, 28 Sep 2025 16:03:34 +0900 Subject: [PATCH 06/11] Update events.rb --- .../core/lib/recordandplayback/generators/events.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index 27927c57791f..5195746fdde8 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -839,6 +839,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 From ddb563c1e2a7d97b98fb18bf656229ab307337af Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 29 Sep 2025 21:14:14 +0900 Subject: [PATCH 07/11] Sometimes Stop occurrs without Start --- .../core/lib/recordandplayback/generators/events.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index 0370a83f9890..0271c109b498 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -957,7 +957,7 @@ 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 do |evt| - if evt[:status] + if evt[:status] # Update matched_external_video_events[-1][:updates] << { :timestamp => evt[:timestamp], :rate => evt[:rate], @@ -965,15 +965,15 @@ def self.match_all_external_video_events(external_video_events) :status => evt[:status], :time => evt[:time] } - elsif evt[:external_video_url] + elsif evt[:external_video_url] # Start matched_external_video_events << { :start_timestamp => evt[:timestamp], :external_video_url => evt[:external_video_url], :updates => [] } - else + else # Stop (sometimes it occurrs without Start e = matched_external_video_events[-1] - e[:stop_timestamp] = evt[:timestamp] + e[:stop_timestamp] = evt[:timestamp] if = e end end #BigBlueButton.logger.info (match_all_external_video_events: "#{matched_external_video_events}") From 2fd5a8114794ccb7dd31ec029bd2bd1c143b6583 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:00:22 +0900 Subject: [PATCH 08/11] fix typo --- .../core/lib/recordandplayback/generators/events.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb index 0271c109b498..e630b6e3f588 100755 --- a/record-and-playback/core/lib/recordandplayback/generators/events.rb +++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb @@ -971,9 +971,9 @@ def self.match_all_external_video_events(external_video_events) :external_video_url => evt[:external_video_url], :updates => [] } - else # Stop (sometimes it occurrs without Start + else # Stop (sometimes it occurrs without Start? -> [if e] is added) e = matched_external_video_events[-1] - e[:stop_timestamp] = evt[:timestamp] if = e + e[:stop_timestamp] = evt[:timestamp] if e end end #BigBlueButton.logger.info (match_all_external_video_events: "#{matched_external_video_events}") From 8f57dfacfe278fd528a224519d0cce1758aedead Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:03:17 +0900 Subject: [PATCH 09/11] Update presentation.yml Add a comment --- record-and-playback/presentation/scripts/presentation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 7f559862c06c..219b1b559cf0 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -10,7 +10,8 @@ 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 +# you need to directly modify /usr/local/bigbluebutton/core/scripts/presentation.yml, +# or overwrite it by /etc/bigbluebutton/recording/presentation.yml include_external_videos: true # For PRODUCTION From 9f3fd6e522bb53b1e0a27d50d8ac5a2e2f130d4c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:25:58 +0900 Subject: [PATCH 10/11] Update presentation.yml add comment --- record-and-playback/presentation/scripts/presentation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml index 219b1b559cf0..0ac1018fd237 100755 --- a/record-and-playback/presentation/scripts/presentation.yml +++ b/record-and-playback/presentation/scripts/presentation.yml @@ -12,6 +12,7 @@ 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 From c7ab7f9ea8362245f970dd06f6c7884e1157301c Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:02:27 +0900 Subject: [PATCH 11/11] FIx a bug position of && broken... --- .../presentation/scripts/publish/presentation.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb index 0c48d990e6e7..656519150e55 100755 --- a/record-and-playback/presentation/scripts/publish/presentation.rb +++ b/record-and-playback/presentation/scripts/publish/presentation.rb @@ -1294,8 +1294,8 @@ def process_external_video_events(_events, package_dir) re_stop_timestamp = re[:stop_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)) + ((start_timestamp <= re_start_timestamp) && (stop_timestamp >= re_stop_timestamp) && + (re_stop_timestamp > re_start_timestamp)) external_videos << { timestamp: timestamp, @@ -1323,7 +1323,8 @@ def process_external_video_events(_events, package_dir) # ((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)) + ((start_timestamp <= re_start_timestamp) && (stop_timestamp >= re_stop_timestamp) && + (re_stop_timestamp > re_start_timestamp)) updates = [] event[:updates].each do |update|