WebRgbaConverter
Converts video frames to RGBA without the pixels ever entering Kotlin memory.
Why this exists at all, when copyPlanesToByteArray is right there
Because that one is twenty times slower on the web, and the difference is not the conversion, it is the crossing. Kotlin/Wasm has no bulk typed-array bridge, so a ByteArray of pixels is filled one byte at a time through a JS call each: the web probe measured that path at 160 to 240 ms per 1080p frame against a 33.3 ms budget, which is not playback. Doing the same work with the conversion in C and the result handed straight to a JS array measured 8.5 to 9.7 ms.
So this class never returns pixels. It fills a JS array the CALLER owns, which in practice is an ImageData.data a canvas is about to draw. The bytes go from the codec module's heap into that array in one JS set, and Kotlin only ever passes two integers and a handle.
The colour a screen needs
The conversion is the one for a picture on a screen, not only a change of encoding. A YCgCo picture uses the YCgCo matrix. A PQ or HLG picture is tone mapped to SDR in C beside the decoder: BT.2020 primaries fold to BT.709, and luminance rolls off from a 1000 nit peak to 203 nit reference white, encoded as gamma 2.2. That is the law KitePlayer's other software paths use, so the canvas shows the same picture they do. Because it runs for every frame drawn, the conversion is swscale's fast one, up to 3 off the colour matrix with each chroma sample repeated across a pair of pixels in a row, where a frame saved as an image or fed to an encoder converts exactly (#164).
Owning one of these
The scratch buffer is reused across frames and grows only when a bigger frame arrives, because a 4K frame is 24.9 MB and allocating that sixty times a second is the other way to lose the measurement. Own one per renderer, not one per frame, and close it with the renderer.
Not thread-safe, which on the web is not a constraint: there are no threads.