diff --git a/record-and-playback/core/lib/recordandplayback/edl.rb b/record-and-playback/core/lib/recordandplayback/edl.rb
index e74c03d0cb150971fbef10377608827c1b623dbe..8588c90694468a07113d60cd608e9066f732d5ba 100644
--- a/record-and-playback/core/lib/recordandplayback/edl.rb
+++ b/record-and-playback/core/lib/recordandplayback/edl.rb
@@ -70,6 +70,7 @@ module BigBlueButton
             exitstatus = BigBlueButton.exec_ret(*cmd)
             raise "postprocess failed, exit code #{exitstatus}" if exitstatus != 0
           end
+          FileUtils.rm(lastoutput)
           lastoutput = ppoutput
         end
       end
diff --git a/record-and-playback/core/lib/recordandplayback/edl/audio.rb b/record-and-playback/core/lib/recordandplayback/edl/audio.rb
index 32e4ed373b62cbb3f29eb3ba77b2e383d074bca7..e6c500c0d87764da1697eb6ab46b9af77d95c1b8 100644
--- a/record-and-playback/core/lib/recordandplayback/edl/audio.rb
+++ b/record-and-playback/core/lib/recordandplayback/edl/audio.rb
@@ -22,9 +22,9 @@ module BigBlueButton
     module Audio
       FFMPEG_AEVALSRC = "aevalsrc=s=48000:c=stereo:exprs=0|0"
       FFMPEG_AFORMAT = "aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo"
-      FFMPEG_WF_CODEC = 'flac'
-      FFMPEG_WF_ARGS = ['-c:a', FFMPEG_WF_CODEC, '-f', 'flac']
-      WF_EXT = 'flac'
+      FFMPEG_WF_CODEC = 'libvorbis'
+      FFMPEG_WF_ARGS = ['-c:a', FFMPEG_WF_CODEC, '-q:a', '2', '-f', 'ogg']
+      WF_EXT = 'ogg'
 
       def self.dump(edl)
         BigBlueButton.logger.debug "EDL Dump:"
diff --git a/record-and-playback/core/lib/recordandplayback/edl/video.rb b/record-and-playback/core/lib/recordandplayback/edl/video.rb
index 269be99a6a887ac3a775857d6508c26bcd40bae6..7bb879d6b24b1adfbe361834e9f87728586947fb 100644
--- a/record-and-playback/core/lib/recordandplayback/edl/video.rb
+++ b/record-and-playback/core/lib/recordandplayback/edl/video.rb
@@ -23,10 +23,9 @@ require 'set'
 module BigBlueButton
   module EDL
     module Video
-      FFMPEG_WF_CODEC = 'mpeg2video'
-      FFMPEG_WF_FRAMERATE = 24
-      FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC.to_s, '-q:v', '2', '-g', (FFMPEG_WF_FRAMERATE * 10).to_s, '-pix_fmt', 'yuv420p', '-r', FFMPEG_WF_FRAMERATE.to_s, '-f', 'mpegts']
-      WF_EXT = 'ts'
+      FFMPEG_WF_CODEC = 'libx264'
+      FFMPEG_WF_ARGS = ['-an', '-codec', FFMPEG_WF_CODEC.to_s, '-preset', 'veryfast', '-crf', '30', '-force_key_frames', 'expr:gte(t,n_forced*10)', '-pix_fmt', 'yuv420p', '-bsf:v', 'h264_mp4toannexb']
+      WF_EXT = 'mp4'
 
       def self.dump(edl)
         BigBlueButton.logger.debug "EDL Dump:"
@@ -254,12 +253,35 @@ module BigBlueButton
 
         BigBlueButton.logger.info "Compositing cuts"
         render = "#{output_basename}.#{WF_EXT}"
+        concat = []
         for i in 0...(edl.length - 1)
           if edl[i][:timestamp] == edl[i][:next_timestamp]
             warn 'Skipping 0-length edl entry'
             next
           end
-          composite_cut(render, edl[i], layout, videoinfo)
+          if video_info(segment).empty?       
+            warn 'Skipping edl entry with no video stream'
+            next
+          end
+          segment = "#{output_basename}_#{i}.#{WF_EXT}"
+          composite_cut(segment, edl[i], layout, videoinfo)
+          concat += [segment]
+        end
+
+        concat_file = "#{output_basename}.txt"
+        File.open(concat_file, 'w') do |outio|
+          concat.each do |segment|
+            outio.write("file #{segment}\n")
+          end
+        end
+
+        ffmpeg_cmd = [*FFMPEG]
+        ffmpeg_cmd += ['-safe', '0', '-f', 'concat', '-i', concat_file , '-c', 'copy', render]
+        exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd)
+        raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0
+
+        concat.each do |segment|
+          File.delete(segment)
         end
 
         return render
@@ -375,7 +397,7 @@ module BigBlueButton
         duration = cut[:next_timestamp] - cut[:timestamp]
         BigBlueButton.logger.info "  Cut start time #{cut[:timestamp]}, duration #{duration}"
 
-        ffmpeg_filter = "color=c=white:s=#{layout[:width]}x#{layout[:height]}:r=24"
+        ffmpeg_filter = "color=c=white:s=#{layout[:width]}x#{layout[:height]}:r=#{layout[:framerate]}"
 
         layout[:areas].each do |layout_area|
           area = cut[:areas][layout_area[:name]]
@@ -488,7 +510,7 @@ module BigBlueButton
             ffmpeg_filter << ",setpts=PTS-#{ms_to_s(seek_offset)}/TB"
             # fps filter fills in frames up to the desired start point, and
             # cuts the video there
-            ffmpeg_filter << ",fps=#{FFMPEG_WF_FRAMERATE}:start_time=#{ms_to_s(video[:timestamp])}"
+            ffmpeg_filter << ",fps=#{layout[:framerate]}:start_time=#{ms_to_s(video[:timestamp])}"
             # Reset the timestamps to start at 0 so that everything is synced
             # for the video tiling, and scale to the desired size.
             ffmpeg_filter << ",setpts=PTS-STARTPTS,scale=#{scale_width}:#{scale_height},setsar=1"
@@ -500,7 +522,7 @@ module BigBlueButton
             # it to length. do that by concatenating a video generated by the
             # color filter. (It would be nice to repeat the last frame instead,
             # but there's no easy way to do that.)
-            ffmpeg_filter << "color=c=white:s=#{tile_width}x#{tile_height}:r=#{FFMPEG_WF_FRAMERATE}"
+            ffmpeg_filter << "color=c=white:s=#{tile_width}x#{tile_height}:r=#{layout[:framerate]}"
             ffmpeg_filter << "[#{pad_name}_pad];"
             ffmpeg_filter << "[#{pad_name}_movie][#{pad_name}_pad]concat=n=2:v=1:a=0[#{pad_name}];"
 
@@ -542,12 +564,10 @@ module BigBlueButton
         ffmpeg_filter << ",trim=end=#{ms_to_s(duration)}"
 
         ffmpeg_cmd = [*FFMPEG]
-        ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-']
+        ffmpeg_cmd += ['-filter_complex', ffmpeg_filter, *FFMPEG_WF_ARGS, '-r', layout[:framerate].to_s, output]
 
-        File.open(output, 'a') do |outio|
-          exitstatus = BigBlueButton.exec_redirect_ret(outio, *ffmpeg_cmd)
-          raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0
-        end
+        exitstatus = BigBlueButton.exec_ret(*ffmpeg_cmd)
+        raise "ffmpeg failed, exit code #{exitstatus}" if exitstatus != 0
 
         return output
       end
diff --git a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb
index ba03a34e367442b47b9a72a545e95b3d44cfe209..9f5129c14487cb1a93944a3d1774aac451a3f31b 100755
--- a/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb
+++ b/record-and-playback/core/lib/recordandplayback/generators/audio_processor.rb
@@ -60,13 +60,13 @@ module BigBlueButton
 
       ogg_format = {
         :extension => 'ogg',
-        :parameters => [ [ '-c:a', 'libvorbis', '-q:a', '2', '-f', 'ogg' ] ]
+        :parameters => [ [ '-c:a', 'copy', '-f', 'ogg' ] ]
       }
       BigBlueButton::EDL.encode(@audio_file, nil, ogg_format, file_basename)
 
       webm_format = {
         :extension => 'webm',
-        :parameters => [ [ '-c:a', 'libvorbis', '-q:a', '2', '-f', 'webm' ] ],
+        :parameters => [ [ '-c:a', 'copy', '-f', 'webm' ] ],
         :postprocess => [ [ 'mkclean', '--quiet', ':input', ':output' ] ]
       }
       BigBlueButton::EDL.encode(@audio_file, nil, webm_format, file_basename)
diff --git a/record-and-playback/core/lib/recordandplayback/generators/video.rb b/record-and-playback/core/lib/recordandplayback/generators/video.rb
index d2ae8846d39de7dd61f7dc25a2f8ba1e6a607988..83b8a7381ed7c744344d8bf16164e507f068c65c 100755
--- a/record-and-playback/core/lib/recordandplayback/generators/video.rb
+++ b/record-and-playback/core/lib/recordandplayback/generators/video.rb
@@ -1,3 +1,4 @@
+
 # Set encoding to utf-8
 # encoding: UTF-8
 
@@ -27,7 +28,7 @@ require File.expand_path('../../edl', __FILE__)
 module BigBlueButton
 
 
-  def BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, output_width, output_height, audio_offset, processed_audio_file, video_formats=['webm'])
+  def BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, output_width, output_height, output_framerate, audio_offset, processed_audio_file, video_formats=['webm'])
     BigBlueButton.logger.info("Processing webcam videos")
 
     events = Nokogiri::XML(File.open("#{temp_dir}/#{meeting_id}/events.xml"))
@@ -42,7 +43,7 @@ module BigBlueButton
     BigBlueButton::EDL::Video.dump(user_video_edl)
 
     user_video_layout = {
-      width: output_width, height: output_height,
+      width: output_width, height: output_height, framerate: output_framerate,
       areas: [ { name: :webcam, x: 0, y: 0, width: output_width, height: output_height } ]
     }
     user_video_file = BigBlueButton::EDL::Video.render(
@@ -55,8 +56,8 @@ module BigBlueButton
           # These settings are appropriate for 640x480 medium quality, and should be tweaked for other resolutions
           # See https://developers.google.com/media/vp9/settings/vod/
           # Increase -threads to max of 4 or increase -speed to max of 4 to speed up processing
-          %w[-c:v libvpx-vp9 -b:v 750K -minrate 375K -maxrate 1088K -crf 33 -quality good -speed 1 -g 240 -tile-columns 1 -threads 2
-             -c:a libopus -b:a 48K
+          %w[-c:v libvpx-vp9 -crf 32 -deadline realtime -cpu-used 8 -force_key_frames expr:gte(t,n_forced*10) -tile-columns 2 -tile-rows 2 -threads 4
+             -c:a copy
              -f webm]
           # Google recommends doing a 2-pass encode for better quality, but it's a lot slower. If you want to do this,
           # comment the lines above, and uncomment the lines below.
@@ -76,7 +77,7 @@ module BigBlueButton
           # Increase -threads (or remove it, to use all cpu cores) to speed up processing
           # You can also change the preset: try 'fast' or 'faster'
           # To change quality, adjust the -crf value. Lower numbers are higher quality.
-          %w[-c:v libx264 -crf 23 -threads 2 -preset medium -g 240
+          %w[-c:v copy
              -c:a aac -b:a 64K
              -f mp4 -movflags faststart]
         ],
@@ -90,7 +91,7 @@ module BigBlueButton
     end
   end
 
-  def BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, output_width, output_height, video_formats=['webm'])
+  def BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, output_width, output_height, output_framerate, video_formats=['webm'])
     BigBlueButton.logger.info("Processing deskshare videos")
 
     events = Nokogiri::XML(File.open("#{temp_dir}/#{meeting_id}/events.xml"))
@@ -107,7 +108,7 @@ module BigBlueButton
     BigBlueButton::EDL::Video.dump(deskshare_video_edl)
 
     deskshare_layout = {
-      width: output_width, height: output_height,
+      width: output_width, height: output_height, framerate: output_framerate,
       areas: [ { name: :deskshare, x: 0, y: 0, width: output_width, height: output_height } ]
     }
 
@@ -121,8 +122,8 @@ module BigBlueButton
           # These settings are appropriate for 1280x720 medium quality, and should be tweaked for other resolutions
           # See https://developers.google.com/media/vp9/settings/vod/
           # Increase -threads to max of 8 or increase -speed to max of 4 to speed up processing
-          %w[-c:v libvpx-vp9 -b:v 1024K -minrate 512K -maxrate 1485K -crf 32 -quality good -speed 2 -g 240 -tile-columns 2 -threads 2
-             -c:a libopus -b:a 48K
+          %w[-c:v libvpx-vp9 -crf 32 -deadline realtime -cpu-used 8 -force_key_frames expr:gte(t,n_forced*10) -tile-columns 2 -tile-rows 2 -threads 4
+             -c:a copy
              -f webm]
           # Google recommends doing a 2-pass encode for better quality, but it's a lot slower. If you want to do this,
           # comment the lines above, and uncomment the lines below.
@@ -142,7 +143,7 @@ module BigBlueButton
           # Increase -threads (or remove it, to use all cpu cores) to speed up processing
           # You can also change the preset: try 'fast' or 'faster'
           # To change quality, adjust the -crf value. Lower numbers are higher quality.
-          %w[-c:v libx264 -crf 23 -threads 2 -preset medium -g 240
+          %w[-c:v copy
              -c:a aac -b:a 64K
              -f mp4 -movflags faststart]
         ],
diff --git a/record-and-playback/presentation/scripts/presentation.yml b/record-and-playback/presentation/scripts/presentation.yml
index 19973ce28c5d26b16802eced3aafc81202e4c783..8101ba5bca4abe1cfb88d4afe9bfdda9479ef41a 100755
--- a/record-and-playback/presentation/scripts/presentation.yml
+++ b/record-and-playback/presentation/scripts/presentation.yml
@@ -1,9 +1,11 @@
 video_output_width: 640
 video_output_height: 480
+video_output_framerate: 15
 # Alternate output size to use when deskshare videos are present
 # Set higher so that deskshare output is higher quality, but uses more space.
 deskshare_output_width: 1280
 deskshare_output_height: 720
+deskshare_output_framerate: 5
 # offset applied to audio in the output video file
 # audio_offset = 1200 means that the audio will be delayed by 1200ms
 audio_offset: 0
diff --git a/record-and-playback/presentation/scripts/process/presentation.rb b/record-and-playback/presentation/scripts/process/presentation.rb
index 55154fc7d87ee0e499f9179a76f086f8275f23d1..5c3a7ab0adac4a515d9611ff2d626862b7bfd54b 100755
--- a/record-and-playback/presentation/scripts/process/presentation.rb
+++ b/record-and-playback/presentation/scripts/process/presentation.rb
@@ -220,21 +220,27 @@ if not FileTest.directory?(target_dir)
         captions.length > 0
       webcam_width = presentation_props['video_output_width']
       webcam_height = presentation_props['video_output_height']
+      webcam_framerate = presentation_props['video_output_framerate']
 
       # Use a higher resolution video canvas if there's broadcast video streams
       if !Dir["#{raw_archive_dir}/video-broadcast/*"].empty?
         webcam_width = presentation_props['deskshare_output_width']
         webcam_height = presentation_props['deskshare_output_height']
+        webcam_framerate = presentation_props['deskshare_output_framerate']
       end
 
+      webcam_framerate = 15 if webcam_framerate.nil?
       processed_audio_file = BigBlueButton::AudioProcessor.get_processed_audio_file("#{temp_dir}/#{meeting_id}", "#{target_dir}/audio")
-      BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, webcam_width, webcam_height, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats'])
+      BigBlueButton.process_webcam_videos(target_dir, temp_dir, meeting_id, webcam_width, webcam_height, webcam_framerate, presentation_props['audio_offset'], processed_audio_file, presentation_props['video_formats'])
     end
 
     if !Dir["#{raw_archive_dir}/deskshare/*"].empty? and presentation_props['include_deskshare']
       deskshare_width = presentation_props['deskshare_output_width']
       deskshare_height = presentation_props['deskshare_output_height']
-      BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, deskshare_width, deskshare_height, presentation_props['video_formats'])
+      deskshare_framerate = presentation_props['deskshare_output_framerate']
+      deskshare_framerate = 5 if deskshare_framerate.nil?
+
+      BigBlueButton.process_deskshare_videos(target_dir, temp_dir, meeting_id, deskshare_width, deskshare_height, deskshare_framerate, presentation_props['video_formats'])
     end
 
     # Copy shared notes from raw files