Class: TestRecorder::FrameWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/test_recorder/frame_writer.rb

Constant Summary collapse

MIN_LAST_FRAME_DURATION =

How long the last frame stays visible at the end of the video. A test usually fails some time after the last repaint, and that last screen is the one worth looking at.

1.0
MAX_LAST_FRAME_DURATION =
5.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io) ⇒ FrameWriter

Returns a new instance of FrameWriter.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/test_recorder/frame_writer.rb', line 10

def initialize(io)
  @container = MatroskaWriter.new(io)
  @first_frame_time = nil
  @last_frame = nil
  @last_write_at = nil
  @last_timestamp_ms = 0
  @duration = nil
  @frame_count = 0
  @finished = false
  # CDP dispatches every screencast frame event on its own thread, and `finish` can run on the
  # main thread while a frame is still in flight, so all access to the state above and to the
  # container is serialized through this.
  @mutex = Mutex.new
end

Instance Attribute Details

#durationObject (readonly)

How many seconds the finished video should run for. ffmpeg does not reliably infer this from the container alone, so stop_and_save passes it to ffmpeg explicitly. Only meaningful after finish has run.



32
33
34
# File 'lib/test_recorder/frame_writer.rb', line 32

def duration
  @duration
end

#frame_countObject (readonly)

Chrome sends a frame as soon as the screencast starts, so a test that fails before it draws anything still leaves behind the one frame of the blank page it started on. Callers use this to tell that apart from a real recording.



37
38
39
# File 'lib/test_recorder/frame_writer.rb', line 37

def frame_count
  @frame_count
end

Instance Method Details

#empty?Boolean

Returns:

  • (Boolean)


25
26
27
# File 'lib/test_recorder/frame_writer.rb', line 25

def empty?
  @last_frame.nil?
end

#finishObject

Repeats the last frame at the end of the recording so that it stays on screen, and so that the video covers the time between the last repaint and the end of the test. That interval is measured with the monotonic clock, so a wall clock adjustment cannot stretch or shrink it.



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/test_recorder/frame_writer.rb', line 54

def finish
  @mutex.synchronize do
    return if @finished

    @finished = true
    return if empty?

    elapsed = (monotonic_time - @last_write_at).clamp(MIN_LAST_FRAME_DURATION, MAX_LAST_FRAME_DURATION)
    write_ms(@last_frame, @last_timestamp_ms + (elapsed * 1000).round)
    @duration = @last_timestamp_ms / 1000.0
  end
end

#write(frame, time) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/test_recorder/frame_writer.rb', line 39

def write(frame, time)
  @mutex.synchronize do
    return if @finished

    @first_frame_time ||= time
    write_at(frame, time)
    @last_frame = frame
    @last_write_at = monotonic_time
    @frame_count += 1
  end
end