class ScreenRecorder::Common

@since 1.0.0-beta11

@api private

Constants

PROCESS_TIMEOUT

Attributes

options[R]
video[R]

Public Class Methods

new(input:, output:, advanced: {}) click to toggle source
# File lib/screen-recorder/common.rb, line 11
def initialize(input:, output:, advanced: {})
  raise Errors::DependencyNotFound unless ffmpeg_exists?

  @options = Options.new(input: input, output: output, advanced: advanced)
  @video   = nil
  @process = nil
end

Public Instance Methods

delete()
Alias for: discard
discard() click to toggle source

Discards the recorded file. Useful in automated testing when a test passes and the recorded file is no longer needed.

# File lib/screen-recorder/common.rb, line 48
def discard
  File.delete options.output
end
Also aliased as: delete
start() click to toggle source

Starts the recording

# File lib/screen-recorder/common.rb, line 22
def start
  ScreenRecorder.logger.debug 'Starting recorder...'
  @video   = nil # New file
  @process = start_ffmpeg
  ScreenRecorder.logger.info 'Recording...'
  @process
end
stop() click to toggle source

Stops the recording

# File lib/screen-recorder/common.rb, line 33
def stop
  ScreenRecorder.logger.debug 'Stopping ffmpeg...'
  exit_code = stop_ffmpeg
  return if exit_code == 1 # recording failed

  ScreenRecorder.logger.debug 'Stopped ffmpeg.'
  ScreenRecorder.logger.info 'Recording complete.'
  @video = prepare_video unless exit_code == 1
end

Private Instance Methods

execute_command(cmd, logfile = nil) click to toggle source

Executes the given command and outputs to the optional logfile

# File lib/screen-recorder/common.rb, line 141
def execute_command(cmd, logfile = nil)
  ScreenRecorder.logger.debug "Executing command: #{cmd}"
  process        = new_process(cmd)
  process.duplex = true
  if logfile
    @log_file         = File.new(logfile, 'w+')
    process.io.stdout = process.io.stderr = @log_file
    @log_file.sync    = true
  end
  process.start
  process
end
ffmpeg_bin() click to toggle source
# File lib/screen-recorder/common.rb, line 101
def ffmpeg_bin
  "#{ScreenRecorder.ffmpeg_binary} -y"
end
ffmpeg_command() click to toggle source

Generates the command line arguments based on the given options.

# File lib/screen-recorder/common.rb, line 109
def ffmpeg_command
  "#{ffmpeg_bin} #{@options.parsed}"
end
ffmpeg_exists?() click to toggle source

Returns true if ffmpeg binary is found.

# File lib/screen-recorder/common.rb, line 116
def ffmpeg_exists?
  return true if FFMPEG.ffmpeg_binary

  false
rescue Errno::ENOENT # Raised when binary is not set in project or found in ENV
  false
end
lines_from_log(position = :last, count = 2) click to toggle source

Returns lines from the log file

# File lib/screen-recorder/common.rb, line 127
def lines_from_log(position = :last, count = 2)
  f     = File.open(options.log)
  lines = f.readlines
  lines = lines.last(count) if position == :last
  lines = lines.first(count) if position == :first
  f.close

  lines.join(' ')
end
new_process(process) click to toggle source

Calls Childprocess.new with OS specific arguments to start the given process.

# File lib/screen-recorder/common.rb, line 158
def new_process(process)
  ChildProcess.posix_spawn = true if RUBY_PLATFORM == 'java' # Support JRuby.
  if OS.windows?
    ChildProcess.new('cmd.exe', '/c', process)
  else
    ChildProcess.new('sh', '-c', process)
  end
end
prepare_video() click to toggle source

Runs ffprobe on the output video file and returns a FFMPEG::Movie object.

# File lib/screen-recorder/common.rb, line 84
def prepare_video
  max_attempts  = 3
  attempts_made = 0
  delay         = 1.0

  begin # Fixes #79
    ScreenRecorder.logger.info 'Running ffprobe to prepare video (output) file.'
    FFMPEG::Movie.new(options.output)
  rescue Errno::EAGAIN, Errno::EACCES
    attempts_made += 1
    ScreenRecorder.logger.error "Failed to run ffprobe. Retrying... (#{attempts_made}/#{max_attempts})"
    sleep(delay)
    retry if attempts_made < max_attempts
    raise
  end
end
start_ffmpeg() click to toggle source

Launches the ffmpeg binary using a generated command based on the given options.

# File lib/screen-recorder/common.rb, line 60
def start_ffmpeg
  process = execute_command(ffmpeg_command, options.log)
  sleep(1.5) # Takes ~1.5s to initialize ffmpeg
  # Check if it exited unexpectedly
  raise FFMPEG::Error, "Failed to start ffmpeg. Reason: #{lines_from_log(:last, 2)}" if process.exited?

  process
end
stop_ffmpeg() click to toggle source

Sends 'q' to the ffmpeg binary to gracefully stop the process. Forcefully terminates it if it takes more than 5s.

# File lib/screen-recorder/common.rb, line 73
def stop_ffmpeg
  @process.io.stdin.puts 'q' # Gracefully exit ffmpeg
  @process.io.stdin.close
  @log_file.close
  wait_for_process_exit(@process)
end
wait_for_process_exit(process) click to toggle source

Waits for given process to exit. Forcefully kills the process if it does not exit within 5 seconds. Returns exit code.

# File lib/screen-recorder/common.rb, line 172
def wait_for_process_exit(process)
  process.poll_for_exit(PROCESS_TIMEOUT)
  process.exit_code # 0
rescue ChildProcess::TimeoutError
  ScreenRecorder.logger.error 'ffmpeg failed to stop. Force killing it...'
  process.stop # Tries increasingly harsher methods to kill the process.
  ScreenRecorder.logger.error 'Forcefully killed ffmpeg. Recording failed!'
  1
end