- System sound: on / off (red while on) turns what the computer plays on and off while
recording, as Mic does the microphone; its state is saved as system_audio in [video].
- modules/Screen_Record: the microphone's switch is now one for either source:
record_set_audio, record_audio_on and record_audio_failed take an Audio_Source_Kind, and the
audio thread opens and closes each as it is turned on and off.
- record-probe -system-toggle. With -sync-test, system sound on at 3 s and off at 7 s keeps only
the beeps at 3 and 5 s (8-12 ms from their flashes), and the track runs the whole recording.
- The toolbar's Mic: on / off (red while on) turns the microphone on and off while recording;
its state is saved as microphone in [video] for the next recording. A microphone that cannot
be opened turns it off again, with a toast saying why.
- modules/Screen_Record: record_set_microphone, record_microphone, record_microphone_failed. The
audio thread opens the microphone only while it is on (so Windows shows it in use only then)
and closes it when turned off. Record_Options.audio_track gives the MP4 a sound track whatever
is on at the start, silent while nothing is, as one cannot be added once the header is
written; the app always asks for it. The sources heard and the failures are kept as they
happen, not only at the start.
- record-probe -mic-toggle: the microphone on at 3 s and off at 7 s. With system sound and with
nothing else on, the track runs the whole recording without a break.
- modules/Screen_Record/audio_source.jai: WASAPI, in loopback from the default output device
(what the computer plays) and from the default input device (the microphone), asked for as
48 kHz stereo float, which Windows converts any device to; a device that will not convert is
taken as it is when it gives 48 kHz float. Packets carry the time they were heard. A device
lost (unplugged, default changed) is opened again from the new default once a second.
- An audio thread mixes them onto the video's own timeline: the same time 0, less the same
pauses (record_pause now keeps one account of time paused for both threads). A packet within
30 ms of where its source left off follows on exactly; a source that sends nothing is silence,
passed 200 ms late. Each 1024 samples every source has covered become an AAC frame.
- encoder.jai: an AAC stream (FFmpeg's aac, 160 kb/s) beside the video, written from the audio
thread; both threads' packets meet in the file under a lock. The remux keeps it.
- [video] system_audio (true) and microphone (false). capture.json: Audio, AudioSources. A toast
when a source asked for could not be opened.
- record-probe: -audio system|mic|both, the sound read back (length, level), and -sync-test,
which flashes a white square and beeps together every 2 s, then finds both in the file.
- Measured: 20 s, beeps 0-21 ms from their flashes, no drift; both sources mixed, 5-18 ms;
a 6 s pause cuts the same 6 s from sound and video; 3 min, 85 of 90 beeps within 3-24 ms and
-14 ms from first to last, with 5 outliers (53-147 ms) where two beeps went missing. Tests:
an MP4 with sound keeps its AAC stream through the remux into the library (30 checks).
- A recording killed mid-way had wrong timestamps in players: its fragmented MP4's header gives a
length of 0, and one cut short never gets the index a clean stop writes. modules/Screen_Record's
record_remux now copies every recording's packets, as they are, into a regular MP4 (one header
first with the length and every frame's index; faststart) before it goes into the library;
should that fail, the fragmented MP4 goes in as it was.
- Keyframes were every 60 frames, which, with frames written only on change, could span minutes
of time, and a crash could lose a fragment that long. One is now forced every 2 s of time
(forced-idr for NVENC and libx264).
- A crash's leftover no longer claims a region at 0, 0 in its capture.json.
- record-probe -remux IN OUT. Tests: a recording cut at its last fragment, as a crash cuts it,
goes in as a regular MP4 of the time before the cut.
- Checked on the recording that showed it: its MP4, cut mid-fragment, rewrites to 76 frames
(5.2 s) with positive frame durations. A 12 s still recording now fragments every ~2 s, and
the app killed 10 s into a recording leaves 8.1 s.
- src/app/screen_record_win32.jai: while recording, a red frame just outside the region (one
window shaped by a window region, clicks let through) and a toolbar beside it (below the
region at its right, else above, else inside a whole screen's top right): a blinking dot,
the time less time paused, the size, frames dropped and screen losses, and Pause/Resume,
Stop, and Discard, confirmed in the toolbar itself ("Yes, discard / No"). GDI windows that
never take the focus, kept out of every capture (WDA_EXCLUDEFROMCAPTURE). Buttons only ask;
screen_record_frame acts on the next frame. On the shutdown thread they are hidden, not
destroyed.
- modules/Screen_Record: record_pause; while paused the source is drained but nothing is sent,
and frames' times leave the pause out, so the video has no gap. Record_Stats.paused_seconds;
Record_Testing.LOSE_DUPLICATION loses Desktop Duplication every 3 s.
- Recordings left in %TEMP%/mcap by a crash are offered at startup: into the library (no window
known; size from its frames, date from its name), deleted, or later.
- [video] in settings.config: fps (1-120, 30), cursor (true), capture (auto, dxgi, wgc); written
into existing files on the next launch.
- The Win32 GDI bindings move to src/base/platform/win32_gdi_bindings.jai, shared by the overlay
and the toolbar.
- -record-region X Y W H and -record-ui-visible for -record; -pause SECONDS and -lose for
record-probe. capture.json's Paused.
- Checked: the toolbar is not in a whole-screen recording's frame; a 10 s pause leaves a 6.2 s
video of a 16 s run at a steady 30 fps (DXGI and WGC); 3 simulated losses in 10 s recover with
no gap. Tests: a leftover's date and its write (22 checks in the recording group); all pass.
- Fixed on the way: a recording's path repeated the temporary folder ("%1" is the first argument).
- record_region, a new [global_hotkeys] action on Ctrl-PrintScreen (Windows only; none on
macOS): starts a recording, or stops the one running.
- The overlay's video mode (screen_capture_win32.jai): red outline and magnifier; the drag held
to the screen it began on; under 240 x 180 (record_size_allowed, in Screen_Record) the outline
is dashed, the label gives the minimum, and letting go takes nothing; a click or Enter takes
the whole screen; no pixels copied. With immediate, no overlay at all: the screen under the
cursor, for PrintScreen while recording.
- src/app/screen_record.jai: records with modules/Screen_Record to %TEMP%/mcap at 30 fps; a
recording that stops by itself keeps what it has, as does quitting (the shutdown step stops it).
The MP4 goes into the library through the stills' jobs: a video .mcap in Screen Captures, the
MP4 its original, a frame of it its rendered image, metadata.json as a still's, capture.json
with Duration, FrameRate, VideoCodec, PosterTime, Encoder, Capture and DroppedFrames; the MP4 is
deleted once it is in. The catalog reads a video's keys from capture.json too.
- The capture thread makes itself aware of each monitor's DPI: ImGui makes only the main thread
aware, and Desktop Duplication refused the app's capture thread with DXGI_ERROR_UNSUPPORTED
(the fallback recorded with Windows.Graphics.Capture, and the log said so).
- -record SECONDS records the screen under the cursor, puts it in the library and exits;
-record-quit quits while recording instead. Both pass on a scratch library.
- Tests: a "screen recording" group (the size rule; an MP4 from the module's encoder written as a
recording reads back, and the catalog takes it as a video). The segment overlay test no longer
depends on the colours chosen in the app's own data folder.
- README, the format's README and the plan describe it.
- modules/Screen_Record/wgc_source.jai: the screen through Windows.Graphics.Capture, with
hand-written bindings for its WinRT interfaces (capture item interop, item, free-threaded frame
pool, frame, session 1-3, IDirect3DDxgiInterfaceAccess, IClosable). A small COM handler on
FrameArrived sets an event per frame; when Windows refuses it the pool is polled each
millisecond. The cursor is drawn by Windows; the yellow border is turned off where the system
allows (Windows 11). A screen that changes size makes the pool again. WinRT's own calls are
loaded from combase.dll at run time: runtimeobject.lib names no DLL for #library.
- capture_gpu.jai: what both sources share (the device on the screen's adapter, the region copy,
the staging ring), so each is only "the next image's region into a texture".
- Record_Options.capture: AUTO (Desktop Duplication, else Windows.Graphics.Capture when it says
fall_back), DXGI or WGC. Sources open on the capture thread, as WinRT objects belong to their
apartment; record_start waits for it to say how that went. Record_Options.testing simulates the
two-GPU failure, forces polling, or keeps the border, for record-probe.
- record-probe: -capture auto|dxgi|wgc, -simulate-unsupported, -poll, -border-test with
-keep-border as its control, -cursor-test; DPI awareness set already is no longer reported as
a failure.
- On the RTX 5080 laptop: auto falls back on the simulated failure; WGC records 4K at 60 fps with
nothing dropped; its cursor and the one cursor.jai draws for duplication are the same; the
border is off for an unpackaged app (0 yellow edge pixels, 14,214 with it kept);
WDA_EXCLUDEFROMCAPTURE is respected; polling holds 30 fps. The plan records the results.
- README: Screen_Record's line, which held a carriage return in place of "\r" since 0.1.3.
- modules/Screen_Record: a region of one screen to H.264 in a fragmented MP4. A capture thread
duplicates the screen (DuplicateOutput1, else DuplicateOutput) on a D3D11 device of its own on
the screen's adapter, copies the region on the GPU, reads it back through three staging
textures and draws the cursor in (colour, masked colour and monochrome shapes). An encoder
thread feeds FFmpeg: h264_nvenc, else h264_amf, h264_qsv, h264_mf, libx264, each tried in turn;
BGRA straight to NVENC, swscale to NV12 or YUV 4:2:0 (BT.709) for the rest. A frame is sent
when the screen or cursor changed and its turn on an fps schedule is due, with a repeat each
second of an unchanged screen; the encoder falling 8 frames behind drops the oldest. A lost
duplication (mode change, secure desktop) is made again and recording goes on. record_start
says fall_back when duplication cannot run (unsupported, too many, rotated screen). Regions
under 240 x 180 are refused, then sizes are rounded down to even; a process whose view of the
desktop is scaled is refused.
- tools/record_probe.jai (jai first.jai - record_probe): lists the screens, records a screen or
region and reports source, encoder, frames captured, sent, written and dropped, time per stage
and file size; -legacy, -cuda, -exclude-test, -animate, -snapshot DIR, -check FILE.
- On the RTX 5080 laptop: 60 s of 4K at 30 fps, 1802 frames, none dropped, 18 MB a minute; every
change kept at 60 fps; plain DuplicateOutput works when DPI aware; a CUDA context changes
nothing; WDA_EXCLUDEFROMCAPTURE is respected; QSV, Media Foundation and libx264 keep up too;
a recording killed mid-way still plays. The two-GPU failure was not reproduced: the screens are
on the NVIDIA GPU in this mode. The plan records the results.